Compare commits

..

37 Commits

Author SHA1 Message Date
github-actions[bot]
560953c37b Bump version to 2.12.2 (#19657)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: crenshaw-dev <350466+crenshaw-dev@users.noreply.github.com>
2024-08-22 23:29:03 -04:00
rumstead
7244c2d5e5 fix(appset): remove cache references (#19652)
Signed-off-by: rumstead <37445536+rumstead@users.noreply.github.com>
2024-08-22 18:09:56 -04:00
gcp-cherry-pick-bot[bot]
b068220503 fix(appset): informer is not a kubernetes informer (#18905) (#19618) (#19636)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-21 20:17:21 -08:00
gcp-cherry-pick-bot[bot]
c873d5c68a fix: Floating title content incorrect for multi-sources (#17274) (#19623) (#19627)
Signed-off-by: Keith Chong <kykchong@redhat.com>
Co-authored-by: Keith Chong <kykchong@redhat.com>
2024-08-21 19:31:48 -04:00
gcp-cherry-pick-bot[bot]
88f85daf52 fix: Parse hostname correctly from repoURL to fetch correct CA cert (#19488) (#19602)
Signed-off-by: Siddhesh Ghadi <sghadi1203@gmail.com>
Co-authored-by: Siddhesh Ghadi <61187612+svghadi@users.noreply.github.com>
Co-authored-by: Jann Fischer <jann@mistrust.net>
2024-08-21 00:48:58 -04:00
github-actions[bot]
26b2039a55 Bump version to 2.12.1 (#19568)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: ishitasequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-08-16 12:39:58 -04:00
Ishita Sequeira
952838cdde fix(appset): cherry-pick - fix appset-in-any-namespace issue with git generators (#19558)
* fix appset-in-any-namespace issue with git generators

Signed-off-by: Ishita Sequeira <ishiseq29@gmail.com>

* fix lint issue

Signed-off-by: Ishita Sequeira <ishiseq29@gmail.com>

---------

Signed-off-by: Ishita Sequeira <ishiseq29@gmail.com>
2024-08-15 17:14:05 -04:00
gcp-cherry-pick-bot[bot]
7af4526666 fix: appset gpg limitation for templated project fields (#19492) (#19534)
* document templating project field while using applicationset git generator and signature verification



* revert changes to generated mocks



* Add check for templated project field and add limitation to the docs



* optimize checks and rephrase documentation



* remove unwanted variable declaration



* Add unit tests



---------

Signed-off-by: Ishita Sequeira <ishiseq29@gmail.com>
Co-authored-by: Ishita Sequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-08-14 12:27:32 -04:00
gcp-cherry-pick-bot[bot]
b156b61e22 fix(appset): missing permissions for cluster install (#19059) (#19430) (#19435)
Signed-off-by: Dmitry Khodorov <el1191@woyd.ru>
Co-authored-by: Dmitry Khodorov <el1191@woyd.ru>
2024-08-08 00:35:24 -04:00
Jae Ryong Song
fd478450e6 fix: docs version regex changed (#18756) (#19352)
Signed-off-by: jasong <jasong@student.42seoul.kr>
2024-08-07 20:36:13 -04:00
github-actions[bot]
ec30a48bce Bump version to 2.12.0 (#19383)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: ishitasequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-08-05 09:24:37 -04:00
gcp-cherry-pick-bot[bot]
57e61b2d8a feat: Add custom health check for cluster-api AWSManagedControlPlane (#19304) (#19360)
* add custom health check for awsmanagedcontrolplane



* chore(deps): bump github.com/aws/aws-sdk-go from 1.55.3 to 1.55.4 (#19295)

Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.55.3 to 1.55.4.
- [Release notes](https://github.com/aws/aws-sdk-go/releases)
- [Commits](https://github.com/aws/aws-sdk-go/compare/v1.55.3...v1.55.4)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
...





* add new line at the end of health_test.yaml file



---------

Signed-off-by: Iulian Taiatu <itaiatu@adobe.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: itaiatu <140485521+itaiatu@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-02 10:55:46 -07:00
github-actions[bot]
6f2ae0dd46 Bump version to 2.12.0-rc5 (#19349)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: ishitasequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-08-01 19:24:32 -04:00
gcp-cherry-pick-bot[bot]
3e31ce9470 fix: ArgoCD 2.11 - Loop of PATCH calls to Application objects (#19340) (#19343)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2024-08-01 16:11:47 -04:00
gcp-cherry-pick-bot[bot]
dee59f3002 feat(rbac): allow validation of fine-grained policy in project (#19338) (#19339)
Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
Co-authored-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
2024-08-01 10:37:55 -04:00
pasha-codefresh
d6c37aab88 Merge commit from fork (#19330) 2024-08-01 01:11:15 +03:00
pasha-codefresh
eaa1972e69 feat: webhook ddos -2.12 (#19329) 2024-08-01 00:53:59 +03:00
gcp-cherry-pick-bot[bot]
004cabba47 fix(applicationset): ensure that older applicationStatus is updated with new required values (#19165) (#19216)
Signed-off-by: wparr-circle <william.parr@circle.com>
Co-authored-by: William Parr <william.parr@circle.com>
2024-07-25 10:12:31 -04:00
gcp-cherry-pick-bot[bot]
d9263101fc chore: revert "feat: removed legacy app tracking label (#13203)" (cherry-pick #19196)
This reverts commit 4d8436b7e1.

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-24 16:31:36 -04:00
Blake Pettersson
cec8504df2 fix: cherry-pick #18761 (v2.12) (#19109)
* fix(applicationset): use requeue after if generate app errors out (#18761)

The `GenerateApplications` can call to external resources like Github
API for instance which might be rate limited or fail. If those requests
somehow fail we should requeue them after some time like (same
reason as e98d3b2a87/applicationset/controllers/applicationset_controller.go (L154)).

For instance, in our environments we were rate limited by Github and the ArgoCD
applicationset controller was logging the following error about every
second or less for every application set using the pull request generator
that we have:
```
time="2024-06-21T14:17:15Z" level=error msg="error generating params" error="error listing repos: error listing pull requests for LedgerHQ/xxx: GET https://api.github.com/repos/LedgerHQ/xxx/pulls?per_page=100: 403 API rate limit exceeded for installation ID xxx. If you reach out to GitHub Support for help, please include the request ID xxx and timestamp 2024-06-xx xxx UTC. [rate reset in 8m18s]" generator="&{0xc000d652c0 0x289a100 {0xc00087bdd0}  [] true}"
time="2024-06-21T14:17:15Z" level=error msg="error generating application from params" applicationset=argocd/xxx error="error listing repos: error listing pull requests for LedgerHQ/xxxx: GET https://api.github.com/repos/LedgerHQ/xxx/pulls?per_page=100: 403 API rate limit exceeded for installation ID xxx. If you reach out to GitHub Support for help, please include the request ID xxx and timestamp 2024-06-xx xxx UTC. [rate reset in 8m18s]" generator="{nil nil nil nil nil &PullRequestGenerator{Github:&PullRequestGeneratorGithub{Owner:LedgerHQ,Repo:xxx,API:,TokenRef:nil,AppSecretName:xxxx,Labels:[argocd/preview],},GitLab:nil,Gitea:nil,BitbucketServer:nil,Filters:[]PullRequestGeneratorFilter{},RequeueAfterSeconds:*1800,Template:ApplicationSetTemplate{ApplicationSetTemplateMeta:ApplicationSetTemplateMeta{Name:,Namespace:,Labels:map[string]string{},Annotations:map[string]string{},Finalizers:[],},Spec:ApplicationSpec{Source:nil,Destination:ApplicationDestination{Server:,Namespace:,Name:,},Project:,SyncPolicy:nil,IgnoreDifferences:[]ResourceIgnoreDifferences{},Info:[]Info{},RevisionHistoryLimit:nil,Sources:[]ApplicationSource{},},},Bitbucket:nil,AzureDevOps:nil,} nil nil nil nil}"
```

Signed-off-by: Arthur Outhenin-Chalandre <arthur.outhenin-chalandre@ledger.fr>

* test: cherry-pick fixes

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>

* chore: please the linter

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>

---------

Signed-off-by: Arthur Outhenin-Chalandre <arthur.outhenin-chalandre@ledger.fr>
Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Arthur Outhenin-Chalandre <arthur.outhenin-chalandre@ledger.fr>
2024-07-18 22:31:40 -04:00
github-actions[bot]
0704aa6506 Bump version to 2.12.0-rc4 (#19060)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: crenshaw-dev <350466+crenshaw-dev@users.noreply.github.com>
2024-07-15 13:28:20 -04:00
Alexandre Gaudreault
511d6e371c chore: bump gitops-engine (#19056)
Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
2024-07-15 12:08:15 -04:00
gcp-cherry-pick-bot[bot]
065f8494cf fix(cli): Get Redis password from secret in loadClusters() (#18951) (#18955)
* Get Redis password from secret in `loadClusters()`



* feat: support redis password in admin stats command



* Simplify code



---------

Signed-off-by: David Wu <155603967+david-wu-octopus@users.noreply.github.com>
Signed-off-by: pashakostohrys <pavel@codefresh.io>
Co-authored-by: david-wu-octopus <155603967+david-wu-octopus@users.noreply.github.com>
Co-authored-by: pashakostohrys <pavel@codefresh.io>
2024-07-05 11:12:24 -04:00
gcp-cherry-pick-bot[bot]
beacacc43d fix(appset): missing permissions (#18829) (#18943) (#18944) 2024-07-04 10:44:58 -04:00
gcp-cherry-pick-bot[bot]
81d454215d remove unwanted updating of source-position in app set command (#18887) (#18897)
Signed-off-by: ishitasequeira <ishiseq29@gmail.com>
Co-authored-by: Ishita Sequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-07-02 13:14:21 -04:00
github-actions[bot]
1237d4ea17 Bump version to 2.12.0-rc3 (#18893)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: crenshaw-dev <350466+crenshaw-dev@users.noreply.github.com>
2024-07-02 13:04:14 -04:00
Michael Crenshaw
b211d3e038 chore: bump gitops-engine (#18871)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-01 13:22:54 -04:00
gcp-cherry-pick-bot[bot]
50c32b53f0 docs: Fix .path to .path.segments go template (#18872) (#18873)
Signed-off-by: Jaeseok Lee <devsunb@gmail.com>
Co-authored-by: Jaeseok Lee <devsunb@gmail.com>
2024-07-01 10:52:36 -04:00
gcp-cherry-pick-bot[bot]
444d332d0a fix: Handle nil health check in post-delete hooks (#18270) (#18767) (#18828)
* fix: Handle nil health check in post-delete hooks



* test: Validate deletion of RBAC resources for post-delete hook



* Update appcontroller_test.go



---------

Signed-off-by: Yonatan Sasson <yonatanxd7@gmail.com>
Signed-off-by: Yonatan Sasson <107778824+Yuni-sa@users.noreply.github.com>
Co-authored-by: Yonatan Sasson <107778824+Yuni-sa@users.noreply.github.com>
2024-06-26 17:21:19 +03:00
gcp-cherry-pick-bot[bot]
573c771d2b fix(webhook): bitbucket and azure not triggering refresh (#18289) (#18765) (#18818)
* fix(webhook): bitbucket and azure webhook not triggering refresh



* update unit test



* fix merge



* adjust logic for reposerver using ls-remote



---------

Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
Co-authored-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
2024-06-26 08:48:16 -04:00
Michael Crenshaw
e616dff2d1 fix(appset): revert "keep reconciling even when params error occurred" (#17062) (#18781)
This reverts commit 86369ca71d.

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-25 15:33:59 -04:00
gcp-cherry-pick-bot[bot]
e36e19de4e chore(deps): bump github.com/hashicorp/go-retryablehttp (#18805) (#18811)
Bumps [github.com/hashicorp/go-retryablehttp](https://github.com/hashicorp/go-retryablehttp) from 0.7.4 to 0.7.7.
- [Changelog](https://github.com/hashicorp/go-retryablehttp/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/go-retryablehttp/compare/v0.7.4...v0.7.7)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/go-retryablehttp
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-25 11:29:12 -04:00
github-actions[bot]
bbfa79ad9e Bump version to 2.12.0-rc2 (#18802)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: crenshaw-dev <350466+crenshaw-dev@users.noreply.github.com>
2024-06-24 16:31:31 -04:00
gcp-cherry-pick-bot[bot]
00fc93b8b2 fix: Bug in edit support in Sources tab; Input to loader (#17588) (#18800) (#18801)
Co-authored-by: Keith Chong <kykchong@redhat.com>
2024-06-24 14:02:06 -04:00
gcp-cherry-pick-bot[bot]
16c20a84b8 fix(server): could not find source for metadata revision (#18744) (#18763) (#18782)
* fix(server): could not find source for metadata revision (#18744)



* lint



* the linter behaves poorly



* fix test



* share logic with chart endpoint



* more intuitive check



* remove debug line



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-24 09:55:56 -04:00
gcp-cherry-pick-bot[bot]
2a9a62eeb7 fix(ui): set project to empty string if undefined (#18732) (#18733)
There are some situations where the project will be `undefined`. When
that happens, attempting to delete a repo won't be possible, since
the backend will be looking for a project with the literal name
`undefined`. To fix this, set an empty string for `undefined|null`
values.

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2024-06-20 12:00:39 +03:00
github-actions[bot]
fe60670885 Bump version to 2.12.0-rc1 (#18716)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: ishitasequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-06-18 09:09:23 -04:00
1501 changed files with 53206 additions and 112816 deletions

13
.gitattributes vendored
View File

@@ -1,13 +0,0 @@
**/*.pb.go linguist-generated=true
**/mocks/*.go linguist-generated=true
assets/swagger.json linguist-generated=true
docs/operator-manual/resource_actions_builtin.md linguist-generated=true
docs/operator-manual/server-commands/argocd-*.md linguist-generated=true
docs/user-guide/commands/argocd_*.md linguist-generated=true
manifests/core-install.yaml linguist-generated=true
manifests/crds/*-crd.yaml linguist-generated=true
manifests/ha/install.yaml linguist-generated=true
manifests/ha/namespace-install.yaml linguist-generated=true
manifests/install.yaml linguist-generated=true
manifests/namespace-install.yaml linguist-generated=true
pkg/apis/api-rules/violation_exceptions.list linguist-generated=true

View File

@@ -1,43 +0,0 @@
---
name: New Dev Tool Request
about: This is a request for adding a new tool for setting up a dev environment.
title: ''
labels: ''
assignees: ''
---
Checklist:
* [ ] 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 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
* [ ] I have given a motivation why this should be added
### The proposer
<-- The username(s) of the person(s) proposing the tool -->
### The proposed tool
<!-- The tool itself, with a link to the tools website -->
### Motivation
<!-- Why this tool would be useful to have in the tree. -->
### Link to PR (Optional)
<!-- A PR adding the tool to the tree -->
### Lead Sponsor(s)
Final approval requires sponsorship from at least one core maintainer.
- @<sponsor-1>
### Co-sponsors
These will be the co-maintainers of the specified tool.
- @<sponsor-1>

View File

@@ -4,13 +4,8 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
open-pull-requests-limit: 20
ignore: ignore:
- dependency-name: k8s.io/* - dependency-name: k8s.io/*
groups:
otel:
patterns:
- "^go.opentelemetry.io/.*"
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" directory: "/"
@@ -31,11 +26,6 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
ignore:
# We use consistent go and node versions across a lot of different files, and updating via dependabot would cause
# drift among those files, instead we let renovate bot handle them.
- dependency-name: "library/golang"
- dependency-name: "library/node"
- package-ecosystem: "docker" - package-ecosystem: "docker"
directory: "/test/container/" directory: "/test/container/"

View File

@@ -13,8 +13,7 @@ on:
env: env:
# Golang version to use across CI steps # Golang version to use across CI steps
# renovate: datasource=golang-version packageName=golang GOLANG_VERSION: '1.22'
GOLANG_VERSION: '1.23.3'
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
@@ -32,7 +31,7 @@ jobs:
docs: ${{ steps.filter.outputs.docs_any_changed }} docs: ${{ steps.filter.outputs.docs_any_changed }}
steps: steps:
- uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 - uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- uses: tj-actions/changed-files@bab30c2299617f6615ec02a68b9a40d10bd21366 # v45.0.5 - uses: tj-actions/changed-files@d6babd6899969df1a11d14c368283ea4436bca78 # v44.5.2
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
@@ -57,7 +56,7 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Download all Go modules - name: Download all Go modules
@@ -78,11 +77,11 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
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@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
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 }}
@@ -105,14 +104,13 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
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@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1
with: with:
# renovate: datasource=go packageName=github.com/golangci/golangci-lint versioning=regex:^v(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?$ version: v1.58.2
version: v1.62.2
args: --verbose args: --verbose
test-go: test-go:
@@ -133,7 +131,7 @@ jobs:
- 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@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages - name: Install required packages
@@ -153,7 +151,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@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
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 }}
@@ -174,7 +172,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@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with: with:
name: test-results name: test-results
path: test-results path: test-results
@@ -197,7 +195,7 @@ jobs:
- 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@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages - name: Install required packages
@@ -217,7 +215,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@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
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 }}
@@ -238,7 +236,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@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with: with:
name: race-results name: race-results
path: test-results/ path: test-results/
@@ -253,7 +251,7 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Create symlink in GOPATH - name: Create symlink in GOPATH
@@ -305,13 +303,12 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup NodeJS - name: Setup NodeJS
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with: with:
# renovate: datasource=node-version packageName=node versioning=node node-version: '21.6.1'
node-version: '22.9.0'
- name: Restore node dependency cache - name: Restore node dependency cache
id: cache-dependencies id: cache-dependencies
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
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') }}
@@ -326,8 +323,6 @@ jobs:
NODE_ENV: production NODE_ENV: production
NODE_ONLINE_ENV: online NODE_ONLINE_ENV: online
HOST_ARCH: amd64 HOST_ARCH: amd64
# If we're on the master branch, set the codecov token so that we upload bundle analysis
CODECOV_TOKEN: ${{ github.ref == 'refs/heads/master' && secrets.CODECOV_TOKEN || '' }}
working-directory: ui/ working-directory: ui/
- name: Run ESLint - name: Run ESLint
run: yarn lint run: yarn lint
@@ -351,7 +346,7 @@ jobs:
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@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
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') }}
@@ -359,41 +354,33 @@ 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@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
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@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: test-results name: test-results
path: test-results path: test-results
- name: combine-go-coverage - name: combine-go-coverage
# We generate coverage reports for all Argo CD components, but only the applicationset-controller, # We generate coverage reports for all Argo CD components, but only the applicationset-controller report
# app-controller, repo-server, and commit-server report contain coverage data. The other components currently # contains coverage data. The other components currently don't shut down gracefully, so no coverage data is
# don't shut down gracefully, so no coverage data is produced. Once those components are fixed, we can add # produced. Once those components are fixed, we can add references to their coverage output directories.
# references to their coverage output directories.
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 -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@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
with: with:
file: 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
if: github.ref == 'refs/heads/master' && github.event_name == 'push' && github.repository == 'argoproj/argo-cd'
uses: codecov/test-results-action@9739113ad922ea0a9abb4b2c0f8bf6a4aa8ef820 # v1.0.1
with:
file: test-results/junit.xml
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
- name: Perform static code analysis using SonarCloud - name: Perform static code analysis using SonarCloud
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@1b442ee39ac3fa7c2acdd410208dcb2bcfaae6c4 # v4.1.0 uses: SonarSource/sonarqube-scan-action@540792c588b5c2740ad2bb4667db5cd46ae678f2 # v2.2
if: env.sonar_secret != '' if: env.sonar_secret != ''
test-e2e: test-e2e:
name: Run end-to-end tests name: Run end-to-end tests
@@ -403,14 +390,14 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
k3s: k3s:
- version: v1.31.0 - version: v1.29.1
# 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.
latest: true latest: true
- version: v1.30.4 - version: v1.28.6
latest: false latest: false
- version: v1.29.8 - version: v1.27.10
latest: false latest: false
- version: v1.28.13 - version: v1.26.13
latest: false latest: false
needs: needs:
- build-go - build-go
@@ -432,7 +419,7 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: GH actions workaround - Kill XSP4 process - name: GH actions workaround - Kill XSP4 process
@@ -451,7 +438,7 @@ jobs:
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@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
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 }}
@@ -477,7 +464,7 @@ 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.41.1 docker pull ghcr.io/dexidp/dex:v2.38.0
docker pull argoproj/argo-cd-ci-builder:v1.0.0 docker pull argoproj/argo-cd-ci-builder:v1.0.0
docker pull redis:7.0.15-alpine docker pull redis:7.0.15-alpine
- name: Create target directory for binaries in the build-process - name: Create target directory for binaries in the build-process
@@ -509,13 +496,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@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.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@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.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

@@ -23,7 +23,7 @@ jobs:
actions: read # for github/codeql-action/init to get workflow details actions: read # for github/codeql-action/init to get workflow details
contents: read # for actions/checkout to fetch code contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/autobuild to send a status report security-events: write # for github/codeql-action/autobuild to send a status report
if: github.repository == 'argoproj/argo-cd' || vars.enable_codeql if: github.repository == 'argoproj/argo-cd'
# CodeQL runs on ubuntu-latest and windows-latest # CodeQL runs on ubuntu-latest and windows-latest
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
@@ -33,7 +33,7 @@ jobs:
# 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@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with: with:
go-version-file: go.mod go-version-file: go.mod

View File

@@ -69,15 +69,15 @@ jobs:
if: ${{ github.ref_type != 'tag'}} if: ${{ github.ref_type != 'tag'}}
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with: with:
go-version: ${{ inputs.go-version }} go-version: ${{ inputs.go-version }}
- name: Install cosign - name: Install cosign
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0
- uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
- uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
- name: Setup tags for container image as a CSV type - name: Setup tags for container image as a CSV type
run: | run: |
@@ -104,7 +104,7 @@ jobs:
echo 'EOF' >> $GITHUB_ENV echo 'EOF' >> $GITHUB_ENV
- name: Login to Quay.io - name: Login to Quay.io
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
with: with:
registry: quay.io registry: quay.io
username: ${{ secrets.quay_username }} username: ${{ secrets.quay_username }}
@@ -112,7 +112,7 @@ jobs:
if: ${{ inputs.quay_image_name && inputs.push }} if: ${{ inputs.quay_image_name && inputs.push }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ secrets.ghcr_username }} username: ${{ secrets.ghcr_username }}
@@ -120,7 +120,7 @@ jobs:
if: ${{ inputs.ghcr_image_name && inputs.push }} if: ${{ inputs.ghcr_image_name && inputs.push }}
- name: Login to dockerhub Container Registry - name: Login to dockerhub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
with: with:
username: ${{ secrets.docker_username }} username: ${{ secrets.docker_username }}
password: ${{ secrets.docker_password }} password: ${{ secrets.docker_password }}
@@ -143,7 +143,7 @@ jobs:
- name: Build and push container image - name: Build and push container image
id: image id: image
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 #v6.10.0 uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 #v5.4.0
with: with:
context: . context: .
platforms: ${{ inputs.platforms }} platforms: ${{ inputs.platforms }}

View File

@@ -52,8 +52,7 @@ jobs:
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 go-version: 1.22
go-version: 1.23.3
platforms: ${{ needs.set-vars.outputs.platforms }} platforms: ${{ needs.set-vars.outputs.platforms }}
push: false push: false
@@ -69,8 +68,7 @@ jobs:
quay_image_name: quay.io/argoproj/argocd:latest quay_image_name: quay.io/argoproj/argocd:latest
ghcr_image_name: ghcr.io/argoproj/argo-cd/argocd:${{ needs.set-vars.outputs.image-tag }} ghcr_image_name: ghcr.io/argoproj/argo-cd/argocd:${{ needs.set-vars.outputs.image-tag }}
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations) # Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
# renovate: datasource=golang-version packageName=golang go-version: 1.22
go-version: 1.23.3
platforms: ${{ needs.set-vars.outputs.platforms }} platforms: ${{ needs.set-vars.outputs.platforms }}
push: true push: true
secrets: secrets:

View File

@@ -64,7 +64,7 @@ jobs:
git stash pop git stash pop
- name: Create pull request - name: Create pull request
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5 uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e # v6.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

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

View File

@@ -10,8 +10,7 @@ on:
permissions: {} permissions: {}
env: env:
# renovate: datasource=golang-version packageName=golang GOLANG_VERSION: '1.22' # Note: go-version must also be set in job argocd-image.with.go-version
GOLANG_VERSION: '1.23.3' # Note: go-version must also be set in job argocd-image.with.go-version
jobs: jobs:
argocd-image: argocd-image:
@@ -24,8 +23,7 @@ jobs:
with: with:
quay_image_name: quay.io/argoproj/argocd:${{ github.ref_name }} quay_image_name: quay.io/argoproj/argocd:${{ github.ref_name }}
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations) # Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
# renovate: datasource=golang-version packageName=golang go-version: 1.22
go-version: 1.23.3
platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
push: true push: true
secrets: secrets:
@@ -69,15 +67,19 @@ jobs:
- name: Fetch all tags - name: Fetch all tags
run: git fetch --force --tags run: git fetch --force --tags
- name: Setup Golang - name: Set GORELEASER_PREVIOUS_TAG # Workaround, GoReleaser uses 'git-describe' to determine a previous tag. Our tags are created in realease branches.
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- 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
echo "GORELEASER_PREVIOUS_TAG=$(go run hack/get-previous-release/get-previous-version-for-release-notes.go ${{ github.ref_name }})" >> $GITHUB_ENV if echo ${{ github.ref_name }} | grep -E -- '-rc1+$';then
echo "GORELEASER_PREVIOUS_TAG=$(git -c 'versionsort.suffix=-rc' tag --list --sort=version:refname | tail -n 2 | head -n 1)" >> $GITHUB_ENV
else
echo "This is not the first release on the branch, Using GoReleaser defaults"
fi
- name: Setup Golang
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Set environment variables for ldflags - name: Set environment variables for ldflags
id: set_ldflag id: set_ldflag
@@ -94,14 +96,14 @@ jobs:
tool-cache: false tool-cache: false
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0 uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0
id: run-goreleaser id: run-goreleaser
with: with:
version: latest version: latest
args: release --clean --timeout 55m args: release --clean --timeout 55m
env: env:
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 }}
- name: Generate subject for provenance - name: Generate subject for provenance
@@ -151,7 +153,7 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
@@ -184,7 +186,7 @@ jobs:
fi fi
cd /tmp && tar -zcf sbom.tar.gz *.spdx cd /tmp && tar -zcf sbom.tar.gz *.spdx
- name: Generate SBOM hash - name: Generate SBOM hash
shell: bash shell: bash
id: sbom-hash id: sbom-hash
@@ -193,15 +195,15 @@ jobs:
# base64 -w0 encodes to base64 and outputs on a single line. # base64 -w0 encodes to base64 and outputs on a single line.
# sha256sum /tmp/sbom.tar.gz ... | base64 -w0 # sha256sum /tmp/sbom.tar.gz ... | base64 -w0
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@7b4da11513bf3f43f9999e90eabced41ab8bb048 # v2.2.0 uses: softprops/action-gh-release@69320dbe05506a9a39fc8ae11030b214ec2d1f87 # v2.0.5
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
files: | files: |
/tmp/sbom.tar.gz /tmp/sbom.tar.gz
sbom-provenance: sbom-provenance:
needs: [generate-sbom] needs: [generate-sbom]
permissions: permissions:
@@ -209,13 +211,13 @@ jobs:
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' 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 refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
with: with:
base64-subjects: "${{ needs.generate-sbom.outputs.hashes }}" base64-subjects: "${{ needs.generate-sbom.outputs.hashes }}"
provenance-name: "argocd-sbom.intoto.jsonl" provenance-name: "argocd-sbom.intoto.jsonl"
upload-assets: true upload-assets: true
post-release: post-release:
needs: needs:
- argocd-image - argocd-image
@@ -289,11 +291,11 @@ jobs:
# Replace the 'project-release: vX.X.X-rcX' line in SECURITY-INSIGHTS.yml # Replace the 'project-release: vX.X.X-rcX' line in SECURITY-INSIGHTS.yml
sed -i "s/project-release: v.*$/project-release: v${{ env.NEW_VERSION }}/" SECURITY-INSIGHTS.yml sed -i "s/project-release: v.*$/project-release: v${{ env.NEW_VERSION }}/" SECURITY-INSIGHTS.yml
# Update the 'commit-hash: XXXXXXX' line in SECURITY-INSIGHTS.yml # Update the 'commit-hash: XXXXXXX' line in SECURITY-INSIGHTS.yml
sed -i "s/commit-hash: .*/commit-hash: ${{ env.COMMIT_HASH }}/" SECURITY-INSIGHTS.yml sed -i "s/commit-hash: .*/commit-hash: ${{ env.NEW_VERSION }}/" SECURITY-INSIGHTS.yml
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@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5 uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e # v6.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

@@ -35,7 +35,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: "Run analysis" - name: "Run analysis"
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
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@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with: with:
name: SARIF file name: SARIF file
path: results.sarif path: results.sarif

1
.gitignore vendored
View File

@@ -1,7 +1,6 @@
.vscode/ .vscode/
.idea/ .idea/
.DS_Store .DS_Store
.run/
vendor/ vendor/
dist/* dist/*
ui/dist/app/* ui/dist/app/*

2
.gitpod.Dockerfile vendored
View File

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

View File

@@ -1,12 +1,9 @@
issues: issues:
exclude: exclude:
- SA1019
- SA5011 - 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 - errcheck
@@ -18,13 +15,9 @@ linters:
- govet - govet
- ineffassign - ineffassign
- misspell - misspell
- perfsprint
- staticcheck - staticcheck
- testifylint - testifylint
- thelper
- unparam
- unused - unused
- usestdlibvars
- whitespace - whitespace
linters-settings: linters-settings:
gocritic: gocritic:
@@ -40,20 +33,11 @@ linters-settings:
- typeSwitchVar - typeSwitchVar
goimports: goimports:
local-prefixes: github.com/argoproj/argo-cd/v2 local-prefixes: github.com/argoproj/argo-cd/v2
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: false
# Optimizes `fmt.Errorf`.
errorf: false
# Optimizes `fmt.Sprintf` with only one argument.
sprintf1: true
# Optimizes into strings concatenation.
strconcat: false
testifylint: testifylint:
enable-all: true enable-all: true
disable: disable:
- error-is-as
- float-compare
- go-require - go-require
run: run:
timeout: 50m timeout: 50m

View File

@@ -1,76 +0,0 @@
# global config
filename: "{{.InterfaceName}}.go"
dir: "{{.InterfaceDir}}/mocks"
outpkg: "mocks"
mockname: "{{.InterfaceName}}"
with-expecter: false
# individual interface config
packages:
github.com/argoproj/argo-cd/v2/applicationset/generators:
interfaces:
Generator:
github.com/argoproj/argo-cd/v2/applicationset/services:
interfaces:
Repos:
github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider:
config:
dir: "applicationset/services/scm_provider/aws_codecommit/mocks"
interfaces:
AWSCodeCommitClient:
AWSTaggingClient:
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/v2/applicationset/utils:
interfaces:
Renderer:
github.com/argoproj/argo-cd/v2/commitserver/commit:
interfaces:
RepoClientFactory:
github.com/argoproj/argo-cd/v2/commitserver/apiclient:
interfaces:
CommitServiceClient:
Clientset:
github.com/argoproj/argo-cd/v2/controller/cache:
interfaces:
LiveStateCache:
github.com/argoproj/argo-cd/v2/reposerver/apiclient:
interfaces:
RepoServerServiceClient:
RepoServerService_GenerateManifestWithFilesClient:
github.com/argoproj/argo-cd/v2/server/application:
interfaces:
Broadcaster:
github.com/argoproj/argo-cd/v2/server/extension:
interfaces:
ApplicationGetter:
ExtensionMetricsRegistry:
ProjectGetter:
RbacEnforcer:
SettingsGetter:
UserGetter:
github.com/argoproj/argo-cd/v2/util/db:
interfaces:
ArgoDB:
github.com/argoproj/argo-cd/v2/util/git:
interfaces:
Client:
github.com/argoproj/argo-cd/v2/util/helm:
interfaces:
Client:
github.com/argoproj/argo-cd/v2/util/io:
interfaces:
TempPaths:
github.com/argoproj/argo-cd/v2/util/notification/argocd:
interfaces:
Service:
# These mocks are not currently used, but they are part of the public API of this package.
github.com/argoproj/argo-cd/v2/pkg/apiclient/session:
interfaces:
SessionServiceServer:
SessionServiceClient:
github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster:
interfaces:
ClusterServiceServer:

View File

@@ -8,4 +8,4 @@ python:
build: build:
os: "ubuntu-22.04" os: "ubuntu-22.04"
tools: tools:
python: "3.12" python: "3.7"

View File

@@ -4,7 +4,7 @@ ARG BASE_IMAGE=docker.io/library/ubuntu:24.04@sha256:3f85b7caad41a95462cf5b787d8
# 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.23.3@sha256:d56c3e08fe5b27729ee3834854ae8f7015af48fd651cd25d1e3bcf3c19830174 AS builder FROM docker.io/library/golang:1.22.4@sha256:c2010b9c2342431a24a2e64e33d9eb2e484af49e72c820e200d332d214d5e61f AS builder
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
@@ -83,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:e643c0b70dca9704dff42e12b17f5b719dbe4f95e6392fc2dfa0c5f02ea8044d AS argocd-ui FROM --platform=$BUILDPLATFORM docker.io/library/node:22.3.0@sha256:5e4044ff6001d06e7748e35bfa4f80c73cf5f5a7360a1b782995e038a01b0585 AS argocd-ui
WORKDIR /src WORKDIR /src
COPY ["ui/package.json", "ui/yarn.lock", "./"] COPY ["ui/package.json", "ui/yarn.lock", "./"]
@@ -101,7 +101,7 @@ RUN HOST_ARCH=$TARGETARCH NODE_ENV='production' NODE_ONLINE_ENV='online' NODE_OP
#################################################################################################### ####################################################################################################
# Argo CD Build stage which performs the actual build of Argo CD binaries # Argo CD Build stage which performs the actual build of Argo CD binaries
#################################################################################################### ####################################################################################################
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.23.3@sha256:d56c3e08fe5b27729ee3834854ae8f7015af48fd651cd25d1e3bcf3c19830174 AS argocd-build FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.22.4@sha256:c2010b9c2342431a24a2e64e33d9eb2e484af49e72c820e200d332d214d5e61f AS argocd-build
WORKDIR /go/src/github.com/argoproj/argo-cd WORKDIR /go/src/github.com/argoproj/argo-cd
@@ -140,8 +140,7 @@ RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-server && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-dex && \ 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-notifications && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-applicationset-controller && \ ln -s /usr/local/bin/argocd /usr/local/bin/argocd-applicationset-controller && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-k8s-auth && \ ln -s /usr/local/bin/argocd /usr/local/bin/argocd-k8s-auth
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-commit-server
USER $ARGOCD_USER_ID USER $ARGOCD_USER_ID
ENTRYPOINT ["/usr/bin/tini", "--"] ENTRYPOINT ["/usr/bin/tini", "--"]

View File

@@ -192,10 +192,6 @@ endif
.PHONY: all .PHONY: all
all: cli image all: cli image
.PHONY: mockgen
mockgen:
./hack/generate-mock.sh
.PHONY: gogen .PHONY: gogen
gogen: gogen:
export GO111MODULE=off export GO111MODULE=off
@@ -233,16 +229,13 @@ clientgen:
clidocsgen: clidocsgen:
go run tools/cmd-docs/main.go go run tools/cmd-docs/main.go
.PHONY: actionsdocsgen
actionsdocsgen:
hack/generate-actions-list.sh
.PHONY: codegen-local .PHONY: codegen-local
codegen-local: mod-vendor-local mockgen gogen protogen clientgen openapigen clidocsgen actionsdocsgen manifests-local notification-docs notification-catalog codegen-local: mod-vendor-local gogen protogen clientgen openapigen clidocsgen manifests-local notification-docs notification-catalog
rm -rf vendor/ rm -rf vendor/
.PHONY: codegen-local-fast .PHONY: codegen-local-fast
codegen-local-fast: mockgen gogen protogen-fast clientgen openapigen clidocsgen manifests-local notification-docs notification-catalog codegen-local-fast: gogen protogen-fast clientgen openapigen clidocsgen manifests-local notification-docs notification-catalog
.PHONY: codegen .PHONY: codegen
codegen: test-tools-image codegen: test-tools-image
@@ -254,7 +247,7 @@ cli: test-tools-image
.PHONY: cli-local .PHONY: cli-local
cli-local: clean-debug cli-local: clean-debug
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -gcflags="all=-N -l" $(COVERAGE_FLAG) -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${CLI_NAME} ./cmd CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build $(COVERAGE_FLAG) -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${CLI_NAME} ./cmd
.PHONY: gen-resources-cli-local .PHONY: gen-resources-cli-local
gen-resources-cli-local: clean-debug gen-resources-cli-local: clean-debug
@@ -472,7 +465,6 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
mkdir -p /tmp/coverage/repo-server mkdir -p /tmp/coverage/repo-server
mkdir -p /tmp/coverage/applicationset-controller mkdir -p /tmp/coverage/applicationset-controller
mkdir -p /tmp/coverage/notification mkdir -p /tmp/coverage/notification
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_SSH_DATA_PATH=/tmp/argo-e2e/app/config/ssh \ ARGOCD_SSH_DATA_PATH=/tmp/argo-e2e/app/config/ssh \
ARGOCD_TLS_DATA_PATH=/tmp/argo-e2e/app/config/tls \ ARGOCD_TLS_DATA_PATH=/tmp/argo-e2e/app/config/tls \
@@ -487,7 +479,6 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
BIN_MODE=$(ARGOCD_BIN_MODE) \ BIN_MODE=$(ARGOCD_BIN_MODE) \
ARGOCD_APPLICATION_NAMESPACES=argocd-e2e-external,argocd-e2e-external-2 \ ARGOCD_APPLICATION_NAMESPACES=argocd-e2e-external,argocd-e2e-external-2 \
ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACES=argocd-e2e-external,argocd-e2e-external-2 \ ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACES=argocd-e2e-external,argocd-e2e-external-2 \
ARGOCD_APPLICATIONSET_CONTROLLER_TOKENREF_STRICT_MODE=true \
ARGOCD_APPLICATIONSET_CONTROLLER_ALLOWED_SCM_PROVIDERS=http://127.0.0.1:8341,http://127.0.0.1:8342,http://127.0.0.1:8343,http://127.0.0.1:8344 \ ARGOCD_APPLICATIONSET_CONTROLLER_ALLOWED_SCM_PROVIDERS=http://127.0.0.1:8341,http://127.0.0.1:8342,http://127.0.0.1:8343,http://127.0.0.1:8344 \
ARGOCD_E2E_TEST=true \ ARGOCD_E2E_TEST=true \
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START} goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START}
@@ -555,7 +546,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 mkdocs; pip install $$(mkdocs get-deps); mkdocs build' $(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'
.PHONY: serve-docs-local .PHONY: serve-docs-local
serve-docs-local: serve-docs-local:
@@ -563,7 +554,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 mkdocs; pip install $$(mkdocs get-deps); 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 -r docs/requirements.txt; 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

1
OWNERS
View File

@@ -1,6 +1,5 @@
owners: owners:
- alexmt - alexmt
- crenshaw-dev
- jessesuen - jessesuen
approvers: approvers:

View File

@@ -1,10 +1,9 @@
controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/app-controller} HOSTNAME=testappcontroller-1 FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} --server-side-diff-enabled=${ARGOCD_APPLICATION_CONTROLLER_SERVER_SIDE_DIFF:-'false'}" controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/app-controller} HOSTNAME=testappcontroller-1 FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} --server-side-diff-enabled=${ARGOCD_APPLICATION_CONTROLLER_SERVER_SIDE_DIFF:-'false'}"
api-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/api-server} FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''}" api-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/api-server} FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''}"
dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v2/cmd gendexcfg -o `pwd`/dist/dex.yaml && (test -f dist/dex.yaml || { echo 'Failed to generate dex configuration'; exit 1; }) && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:$(grep "image: ghcr.io/dexidp/dex" manifests/base/dex/argocd-dex-server-deployment.yaml | cut -d':' -f3) dex serve /dex.yaml" dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v2/cmd gendexcfg -o `pwd`/dist/dex.yaml && (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: bash -c "if [ \"$ARGOCD_REDIS_LOCAL\" = 'true' ]; then redis-server --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; else docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} docker.io/library/redis:$(grep "image: redis" manifests/base/redis/argocd-redis-deployment.yaml | cut -d':' -f3) --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; fi"
repo-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/repo-server} FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-repo-server ARGOCD_GPG_ENABLED=${ARGOCD_GPG_ENABLED:-false} $COMMAND --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --otlp-address=${ARGOCD_OTLP_ADDRESS}" repo-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "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}"
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

View File

@@ -7,7 +7,8 @@
[![Integration tests](https://github.com/argoproj/argo-cd/workflows/Integration%20tests/badge.svg?branch=master)](https://github.com/argoproj/argo-cd/actions?query=workflow%3A%22Integration+tests%22) [![Integration tests](https://github.com/argoproj/argo-cd/workflows/Integration%20tests/badge.svg?branch=master)](https://github.com/argoproj/argo-cd/actions?query=workflow%3A%22Integration+tests%22)
[![codecov](https://codecov.io/gh/argoproj/argo-cd/branch/master/graph/badge.svg)](https://codecov.io/gh/argoproj/argo-cd) [![codecov](https://codecov.io/gh/argoproj/argo-cd/branch/master/graph/badge.svg)](https://codecov.io/gh/argoproj/argo-cd)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4486/badge)](https://bestpractices.coreinfrastructure.org/projects/4486) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4486/badge)](https://bestpractices.coreinfrastructure.org/projects/4486)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/argoproj/argo-cd/badge)](https://scorecard.dev/viewer/?uri=github.com/argoproj/argo-cd) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/argoproj/argo-cd/badge)](https://api.securityscorecards.dev/projects/github.com/argoproj/argo-cd)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fargoproj%2Fargo-cd.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fargoproj%2Fargo-cd?ref=badge_shield)
**Social:** **Social:**
[![Twitter Follow](https://img.shields.io/twitter/follow/argoproj?style=social)](https://twitter.com/argoproj) [![Twitter Follow](https://img.shields.io/twitter/follow/argoproj?style=social)](https://twitter.com/argoproj)
@@ -56,7 +57,7 @@ Participation in the Argo CD project is governed by the [CNCF Code of Conduct](h
### Blogs and Presentations ### Blogs and Presentations
1. [Awesome-Argo: A Curated List of Awesome Projects and Resources Related to Argo](https://github.com/terrytangyuan/awesome-argo) 1. [Awesome-Argo: A Curated List of Awesome Projects and Resources Related to Argo](https://github.com/terrytangyuan/awesome-argo)
1. [Unveil the Secret Ingredients of Continuous Delivery at Enterprise Scale with Argo CD](https://akuity.io/blog/secret-ingredients-of-continuous-delivery-at-enterprise-scale-with-argocd/) 1. [Unveil the Secret Ingredients of Continuous Delivery at Enterprise Scale with Argo CD](https://akuity.io/blog/unveil-the-secret-ingredients-of-continuous-delivery-at-enterprise-scale-with-argocd-kubecon-china-2021/)
1. [GitOps Without Pipelines With ArgoCD Image Updater](https://youtu.be/avPUQin9kzU) 1. [GitOps Without Pipelines With ArgoCD Image Updater](https://youtu.be/avPUQin9kzU)
1. [Combining Argo CD (GitOps), Crossplane (Control Plane), And KubeVela (OAM)](https://youtu.be/eEcgn_gU3SM) 1. [Combining Argo CD (GitOps), Crossplane (Control Plane), And KubeVela (OAM)](https://youtu.be/eEcgn_gU3SM)
1. [How to Apply GitOps to Everything - Combining Argo CD and Crossplane](https://youtu.be/yrj4lmScKHQ) 1. [How to Apply GitOps to Everything - Combining Argo CD and Crossplane](https://youtu.be/yrj4lmScKHQ)

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: 74a367d10e7110209610ba3ec225539ebe5f7522 commit-hash: b71277c6beb949d0199d647a582bc25822b88838
project-url: https://github.com/argoproj/argo-cd project-url: https://github.com/argoproj/argo-cd
project-release: v2.14.0 project-release: v2.9.0-rc3
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:

View File

@@ -11,13 +11,10 @@ Currently, the following organizations are **officially** using Argo CD:
1. [7shifts](https://www.7shifts.com/) 1. [7shifts](https://www.7shifts.com/)
1. [Adevinta](https://www.adevinta.com/) 1. [Adevinta](https://www.adevinta.com/)
1. [Adfinis](https://adfinis.com) 1. [Adfinis](https://adfinis.com)
1. [Adobe](https://www.adobe.com/)
1. [Adventure](https://jp.adventurekk.com/) 1. [Adventure](https://jp.adventurekk.com/)
1. [Adyen](https://www.adyen.com) 1. [Adyen](https://www.adyen.com)
1. [AirQo](https://airqo.net/) 1. [AirQo](https://airqo.net/)
1. [Akuity](https://akuity.io/) 1. [Akuity](https://akuity.io/)
1. [Alarm.com](https://alarm.com/)
1. [Alauda](https://alauda.io/)
1. [Albert Heijn](https://ah.nl/) 1. [Albert Heijn](https://ah.nl/)
1. [Alibaba Group](https://www.alibabagroup.com/) 1. [Alibaba Group](https://www.alibabagroup.com/)
1. [Allianz Direct](https://www.allianzdirect.de/) 1. [Allianz Direct](https://www.allianzdirect.de/)
@@ -32,28 +29,23 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Arctiq Inc.](https://www.arctiq.ca) 1. [Arctiq Inc.](https://www.arctiq.ca)
2. [Arturia](https://www.arturia.com) 2. [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. [Autodesk](https://www.autodesk.com) 1. [Autodesk](https://www.autodesk.com)
1. [Axians ACSP](https://www.axians.fr) 1. [Axians ACSP](https://www.axians.fr)
1. [Axual B.V.](https://axual.com) 1. [Axual B.V.](https://axual.com)
1. [Back Market](https://www.backmarket.com) 1. [Back Market](https://www.backmarket.com)
1. [Bajaj Finserv Health Ltd.](https://www.bajajfinservhealth.in)
1. [Baloise](https://www.baloise.com) 1. [Baloise](https://www.baloise.com)
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. [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/)
1. [Boozt](https://www.booztgroup.com/) 1. [Boozt](https://www.booztgroup.com/)
1. [Bosch](https://www.bosch.com/)
1. [Boticario](https://www.boticario.com.br/) 1. [Boticario](https://www.boticario.com.br/)
1. [Broker Consulting, a.s.](https://www.bcas.cz/en/) 1. [Broker Consulting, a.s.](https://www.bcas.cz/en/)
1. [Bulder Bank](https://bulderbank.no) 1. [Bulder Bank](https://bulderbank.no)
1. [Cabify](https://cabify.com/en)
1. [CAM](https://cam-inc.co.jp) 1. [CAM](https://cam-inc.co.jp)
1. [Camptocamp](https://camptocamp.com) 1. [Camptocamp](https://camptocamp.com)
1. [Candis](https://www.candis.io) 1. [Candis](https://www.candis.io)
@@ -70,16 +62,13 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Cisco ET&I](https://eti.cisco.com/) 1. [Cisco ET&I](https://eti.cisco.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. [CloudGeometry](https://www.cloudgeometry.io/) 1. [CloudGeometry](https://www.cloudgeometry.io/)
1. [Cloudmate](https://cloudmt.co.kr/) 1. [Cloudmate](https://cloudmt.co.kr/)
1. [Cloudogu](https://cloudogu.com/) 1. [Cloudogu](https://cloudogu.com/)
1. [Cobalt](https://www.cobalt.io/) 1. [Cobalt](https://www.cobalt.io/)
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. [Commonbond](https://commonbond.co/) 1. [Commonbond](https://commonbond.co/)
1. [Compatio.AI](https://compatio.ai/)
1. [Contlo](https://contlo.com/) 1. [Contlo](https://contlo.com/)
1. [Coralogix](https://coralogix.com/) 1. [Coralogix](https://coralogix.com/)
1. [Crédit Agricole CIB](https://www.ca-cib.com) 1. [Crédit Agricole CIB](https://www.ca-cib.com)
@@ -89,7 +78,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [D2iQ](https://www.d2iq.com) 1. [D2iQ](https://www.d2iq.com)
1. [DaoCloud](https://daocloud.io/) 1. [DaoCloud](https://daocloud.io/)
1. [Datarisk](https://www.datarisk.io/) 1. [Datarisk](https://www.datarisk.io/)
1. [Daydream](https://daydream.ing)
1. [Deloitte](https://www.deloitte.com/) 1. [Deloitte](https://www.deloitte.com/)
1. [Deutsche Telekom AG](https://telekom.com) 1. [Deutsche Telekom AG](https://telekom.com)
1. [Devopsi - Poland Software/DevOps Consulting](https://devopsi.pl/) 1. [Devopsi - Poland Software/DevOps Consulting](https://devopsi.pl/)
@@ -120,13 +108,10 @@ Currently, the following organizations are **officially** using Argo CD:
1. [freee](https://corp.freee.co.jp/en/company/) 1. [freee](https://corp.freee.co.jp/en/company/)
1. [Freshop, Inc](https://www.freshop.com/) 1. [Freshop, Inc](https://www.freshop.com/)
1. [Future PLC](https://www.futureplc.com/) 1. [Future PLC](https://www.futureplc.com/)
1. [Flagler Health](https://www.flaglerhealth.io/)
1. [G DATA CyberDefense AG](https://www.gdata-software.com/) 1. [G DATA CyberDefense AG](https://www.gdata-software.com/)
1. [G-Research](https://www.gresearch.com/teams/open-source-software/)
1. [Garner](https://www.garnercorp.com) 1. [Garner](https://www.garnercorp.com)
1. [Generali Deutschland AG](https://www.generali.de/) 1. [Generali Deutschland AG](https://www.generali.de/)
1. [Gepardec](https://gepardec.com/) 1. [Gepardec](https://gepardec.com/)
1. [Getir](https://getir.com)
1. [GetYourGuide](https://www.getyourguide.com/) 1. [GetYourGuide](https://www.getyourguide.com/)
1. [Gitpod](https://www.gitpod.io) 1. [Gitpod](https://www.gitpod.io)
1. [Gllue](https://gllue.com) 1. [Gllue](https://gllue.com)
@@ -143,7 +128,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Groww](https://groww.in) 1. [Groww](https://groww.in)
1. [Grupo MasMovil](https://grupomasmovil.com/en/) 1. [Grupo MasMovil](https://grupomasmovil.com/en/)
1. [Handelsbanken](https://www.handelsbanken.se) 1. [Handelsbanken](https://www.handelsbanken.se)
1. [Hazelcast](https://hazelcast.com/)
1. [Healy](https://www.healyworld.net) 1. [Healy](https://www.healyworld.net)
1. [Helio](https://helio.exchange) 1. [Helio](https://helio.exchange)
1. [Hetki](https://hetki.ai) 1. [Hetki](https://hetki.ai)
@@ -162,7 +146,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Index Exchange](https://www.indexexchange.com/) 1. [Index Exchange](https://www.indexexchange.com/)
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. [Intuit](https://www.intuit.com/) 1. [Intuit](https://www.intuit.com/)
1. [Jellysmack](https://www.jellysmack.com) 1. [Jellysmack](https://www.jellysmack.com)
1. [Joblift](https://joblift.com/) 1. [Joblift](https://joblift.com/)
@@ -172,7 +155,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Karrot](https://www.daangn.com/) 1. [Karrot](https://www.daangn.com/)
1. [KarrotPay](https://www.daangnpay.com/) 1. [KarrotPay](https://www.daangnpay.com/)
1. [Kasa](https://kasa.co.kr/) 1. [Kasa](https://kasa.co.kr/)
1. [Kave Home](https://kavehome.com)
1. [Keeeb](https://www.keeeb.com/) 1. [Keeeb](https://www.keeeb.com/)
1. [KelkooGroup](https://www.kelkoogroup.com) 1. [KelkooGroup](https://www.kelkoogroup.com)
1. [Keptn](https://keptn.sh) 1. [Keptn](https://keptn.sh)
@@ -185,8 +167,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Kurly](https://www.kurly.com/) 1. [Kurly](https://www.kurly.com/)
1. [Kvist](https://kvistsolutions.com) 1. [Kvist](https://kvistsolutions.com)
1. [Kyriba](https://www.kyriba.com/) 1. [Kyriba](https://www.kyriba.com/)
1. [LeFigaro](https://www.lefigaro.fr/)
1. [Lely](https://www.lely.com/)
1. [LexisNexis](https://www.lexisnexis.com/) 1. [LexisNexis](https://www.lexisnexis.com/)
1. [Lian Chu Securities](https://lczq.com) 1. [Lian Chu Securities](https://lczq.com)
1. [Liatrio](https://www.liatrio.com) 1. [Liatrio](https://www.liatrio.com)
@@ -216,16 +196,12 @@ Currently, the following organizations are **officially** using Argo CD:
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. [MOO Print](https://www.moo.com/) 1. [MOO Print](https://www.moo.com/)
1. [Mozilla](https://www.mozilla.org)
1. [MTN Group](https://www.mtn.com/) 1. [MTN Group](https://www.mtn.com/)
1. [Municipality of The Hague](https://www.denhaag.nl/)
1. [My Job Glasses](https://myjobglasses.com)
1. [Natura &Co](https://naturaeco.com/) 1. [Natura &Co](https://naturaeco.com/)
1. [Nethopper](https://nethopper.io) 1. [Nethopper](https://nethopper.io)
1. [New Relic](https://newrelic.com/) 1. [New Relic](https://newrelic.com/)
1. [Nextbasket](https://nextbasket.com) 1. [Nextbasket](https://nextbasket.com)
1. [Nextdoor](https://nextdoor.com/) 1. [Nextdoor](https://nextdoor.com/)
1. [Next Fit Sistemas](https://nextfit.com.br/)
1. [Nikkei](https://www.nikkei.co.jp/nikkeiinfo/en/) 1. [Nikkei](https://www.nikkei.co.jp/nikkeiinfo/en/)
1. [Nitro](https://gonitro.com) 1. [Nitro](https://gonitro.com)
1. [NYCU, CS IT Center](https://it.cs.nycu.edu.tw) 1. [NYCU, CS IT Center](https://it.cs.nycu.edu.tw)
@@ -237,7 +213,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [omegaUp](https://omegaUp.com) 1. [omegaUp](https://omegaUp.com)
1. [Omni](https://omni.se/) 1. [Omni](https://omni.se/)
1. [Oncourse Home Solutions](https://oncoursehome.com/) 1. [Oncourse Home Solutions](https://oncoursehome.com/)
1. [Open Analytics](https://openanalytics.eu)
1. [openEuler](https://openeuler.org) 1. [openEuler](https://openeuler.org)
1. [openGauss](https://opengauss.org/) 1. [openGauss](https://opengauss.org/)
1. [OpenGov](https://opengov.com) 1. [OpenGov](https://opengov.com)
@@ -249,7 +224,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Optoro](https://www.optoro.com/) 1. [Optoro](https://www.optoro.com/)
1. [Orbital Insight](https://orbitalinsight.com/) 1. [Orbital Insight](https://orbitalinsight.com/)
1. [Oscar Health Insurance](https://hioscar.com/) 1. [Oscar Health Insurance](https://hioscar.com/)
1. [Outpost24](https://outpost24.com/)
1. [p3r](https://www.p3r.one/) 1. [p3r](https://www.p3r.one/)
1. [Packlink](https://www.packlink.com/) 1. [Packlink](https://www.packlink.com/)
1. [PagerDuty](https://www.pagerduty.com/) 1. [PagerDuty](https://www.pagerduty.com/)
@@ -271,14 +245,12 @@ Currently, the following organizations are **officially** using Argo CD:
1. [PostFinance](https://github.com/postfinance) 1. [PostFinance](https://github.com/postfinance)
1. [Preferred Networks](https://preferred.jp/en/) 1. [Preferred Networks](https://preferred.jp/en/)
1. [Previder BV](https://previder.nl) 1. [Previder BV](https://previder.nl)
1. [Priceline](https://priceline.com)
1. [Procore](https://www.procore.com) 1. [Procore](https://www.procore.com)
1. [Productboard](https://www.productboard.com/) 1. [Productboard](https://www.productboard.com/)
1. [Prudential](https://prudential.com.sg) 1. [Prudential](https://prudential.com.sg)
1. [PT Boer Technology (Btech)](https://btech.id/) 1. [PT Boer Technology (Btech)](https://btech.id/)
1. [PUBG](https://www.pubg.com) 1. [PUBG](https://www.pubg.com)
1. [Puzzle ITC](https://www.puzzle.ch/) 1. [Puzzle ITC](https://www.puzzle.ch/)
1. [Pvotal Technologies](https://pvotal.tech/)
1. [Qonto](https://qonto.com) 1. [Qonto](https://qonto.com)
1. [QuintoAndar](https://quintoandar.com.br) 1. [QuintoAndar](https://quintoandar.com.br)
1. [Quipper](https://www.quipper.com/) 1. [Quipper](https://www.quipper.com/)
@@ -288,7 +260,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Redpill Linpro](https://www.redpill-linpro.com/) 1. [Redpill Linpro](https://www.redpill-linpro.com/)
1. [Reenigne Cloud](https://reenigne.ca) 1. [Reenigne Cloud](https://reenigne.ca)
1. [reev.com](https://www.reev.com/) 1. [reev.com](https://www.reev.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](https://www.risecard.eu/) 1. [Rise](https://www.risecard.eu/)
@@ -305,17 +276,13 @@ Currently, the following organizations are **officially** using Argo CD:
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. [Semgrep](https://semgrep.com) 1. [Semgrep](https://semgrep.com)
1. [Shield](https://shield.com) 1. [Shield](https://shield.com)
1. [SI Analytics](https://si-analytics.ai) 1. [SI Analytics](https://si-analytics.ai)
1. [Sidewalk Entertainment](https://sidewalkplay.com/)
1. [Skit](https://skit.ai/) 1. [Skit](https://skit.ai/)
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)
1. [Smilegate Stove](https://www.onstove.com/)
1. [Smood.ch](https://www.smood.ch/) 1. [Smood.ch](https://www.smood.ch/)
1. [Snapp](https://snapp.ir/) 1. [Snapp](https://snapp.ir/)
1. [Snyk](https://snyk.io/) 1. [Snyk](https://snyk.io/)
@@ -339,12 +306,10 @@ Currently, the following organizations are **officially** using Argo CD:
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/)
1. [TBC Bank](https://tbcbank.ge/)
1. [Techcombank](https://www.techcombank.com.vn/trang-chu) 1. [Techcombank](https://www.techcombank.com.vn/trang-chu)
1. [Technacy](https://www.technacy.it/) 1. [Technacy](https://www.technacy.it/)
1. [Telavita](https://www.telavita.com.br/) 1. [Telavita](https://www.telavita.com.br/)
1. [Tesla](https://tesla.com/) 1. [Tesla](https://tesla.com/)
1. [TextNow](https://www.textnow.com/)
1. [The Scale Factory](https://www.scalefactory.com/) 1. [The Scale Factory](https://www.scalefactory.com/)
1. [ThousandEyes](https://www.thousandeyes.com/) 1. [ThousandEyes](https://www.thousandeyes.com/)
1. [Ticketmaster](https://ticketmaster.com) 1. [Ticketmaster](https://ticketmaster.com)
@@ -392,5 +357,4 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Yubo](https://www.yubo.live/) 1. [Yubo](https://www.yubo.live/)
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. [ZipRecuiter](https://www.ziprecruiter.com/)
1. [ZOZO](https://corp.zozo.com/) 1. [ZOZO](https://corp.zozo.com/)

View File

@@ -1 +1 @@
2.14.0 2.12.2

View File

@@ -18,9 +18,6 @@ import (
"context" "context"
"fmt" "fmt"
"reflect" "reflect"
"runtime/debug"
"sort"
"strconv"
"strings" "strings"
"time" "time"
@@ -35,7 +32,6 @@ import (
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
"k8s.io/client-go/util/retry"
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
@@ -45,15 +41,14 @@ import (
"sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/predicate"
"github.com/argoproj/argo-cd/v2/applicationset/controllers/template"
"github.com/argoproj/argo-cd/v2/applicationset/generators" "github.com/argoproj/argo-cd/v2/applicationset/generators"
"github.com/argoproj/argo-cd/v2/applicationset/metrics"
"github.com/argoproj/argo-cd/v2/applicationset/status"
"github.com/argoproj/argo-cd/v2/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/argoproj/argo-cd/v2/common" "github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/util/db" "github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v2/util/glob"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
argoutil "github.com/argoproj/argo-cd/v2/util/argo" argoutil "github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/argo/normalizers" "github.com/argoproj/argo-cd/v2/util/argo/normalizers"
@@ -80,6 +75,7 @@ type ApplicationSetReconciler struct {
Recorder record.EventRecorder Recorder record.EventRecorder
Generators map[string]generators.Generator Generators map[string]generators.Generator
ArgoDB db.ArgoDB ArgoDB db.ArgoDB
ArgoAppClientset appclientset.Interface
KubeClientset kubernetes.Interface KubeClientset kubernetes.Interface
Policy argov1alpha1.ApplicationsSyncPolicy Policy argov1alpha1.ApplicationsSyncPolicy
EnablePolicyOverride bool EnablePolicyOverride bool
@@ -90,31 +86,17 @@ type ApplicationSetReconciler struct {
SCMRootCAPath string SCMRootCAPath string
GlobalPreservedAnnotations []string GlobalPreservedAnnotations []string
GlobalPreservedLabels []string GlobalPreservedLabels []string
Metrics *metrics.ApplicationsetMetrics
} }
// +kubebuilder:rbac:groups=argoproj.io,resources=applicationsets,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=argoproj.io,resources=applicationsets,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=argoproj.io,resources=applicationsets/status,verbs=get;update;patch // +kubebuilder:rbac:groups=argoproj.io,resources=applicationsets/status,verbs=get;update;patch
func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) { func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
startReconcile := time.Now()
logCtx := log.WithField("applicationset", req.NamespacedName) logCtx := log.WithField("applicationset", req.NamespacedName)
defer func() {
if rec := recover(); rec != nil {
logCtx.Errorf("Recovered from panic: %+v\n%s", rec, debug.Stack())
result = ctrl.Result{}
var ok bool
err, ok = rec.(error)
if !ok {
err = fmt.Errorf("%v", r)
}
}
}()
var applicationSetInfo argov1alpha1.ApplicationSet var applicationSetInfo argov1alpha1.ApplicationSet
parametersGenerated := false parametersGenerated := false
startTime := time.Now()
if err := r.Get(ctx, req.NamespacedName, &applicationSetInfo); err != nil { if err := r.Get(ctx, req.NamespacedName, &applicationSetInfo); err != nil {
if client.IgnoreNotFound(err) != nil { if client.IgnoreNotFound(err) != nil {
logCtx.WithError(err).Infof("unable to get ApplicationSet: '%v' ", err) logCtx.WithError(err).Infof("unable to get ApplicationSet: '%v' ", err)
@@ -122,10 +104,6 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
return ctrl.Result{}, client.IgnoreNotFound(err) return ctrl.Result{}, client.IgnoreNotFound(err)
} }
defer func() {
r.Metrics.ObserveReconcile(&applicationSetInfo, time.Since(startTime))
}()
// Do not attempt to further reconcile the ApplicationSet if it is being deleted. // Do not attempt to further reconcile the ApplicationSet if it is being deleted.
if applicationSetInfo.ObjectMeta.DeletionTimestamp != nil { if applicationSetInfo.ObjectMeta.DeletionTimestamp != nil {
appsetName := applicationSetInfo.ObjectMeta.Name appsetName := applicationSetInfo.ObjectMeta.Name
@@ -153,7 +131,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
// Log a warning if there are unrecognized generators // Log a warning if there are unrecognized generators
_ = utils.CheckInvalidGenerators(&applicationSetInfo) _ = utils.CheckInvalidGenerators(&applicationSetInfo)
// desiredApplications is the main list of all expected Applications from all generators in this appset. // desiredApplications is the main list of all expected Applications from all generators in this appset.
desiredApplications, applicationSetReason, err := template.GenerateApplications(logCtx, applicationSetInfo, r.Generators, r.Renderer, r.Client) desiredApplications, applicationSetReason, err := r.generateApplications(logCtx, applicationSetInfo)
if err != nil { if err != nil {
_ = r.setApplicationSetStatusCondition(ctx, _ = r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo, &applicationSetInfo,
@@ -259,8 +237,20 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
if r.EnableProgressiveSyncs { if r.EnableProgressiveSyncs {
// trigger appropriate application syncs if RollingSync strategy is enabled // trigger appropriate application syncs if RollingSync strategy is enabled
if progressiveSyncsRollingSyncStrategyEnabled(&applicationSetInfo) { if progressiveSyncsStrategyEnabled(&applicationSetInfo, "RollingSync") {
validApps = r.syncValidApplications(logCtx, &applicationSetInfo, appSyncMap, appMap, validApps) validApps, err = r.syncValidApplications(logCtx, &applicationSetInfo, appSyncMap, appMap, validApps)
if err != nil {
_ = r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo,
argov1alpha1.ApplicationSetCondition{
Type: argov1alpha1.ApplicationSetConditionErrorOccurred,
Message: err.Error(),
Reason: argov1alpha1.ApplicationSetReasonSyncApplicationError,
Status: argov1alpha1.ApplicationSetConditionStatusTrue,
}, parametersGenerated,
)
return ctrl.Result{}, err
}
} }
} }
@@ -347,7 +337,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
requeueAfter = ReconcileRequeueOnValidationError requeueAfter = ReconcileRequeueOnValidationError
} }
logCtx.WithField("requeueAfter", requeueAfter).Info("end reconcile in ", time.Since(startReconcile)) logCtx.WithField("requeueAfter", requeueAfter).Info("end reconcile")
return ctrl.Result{ return ctrl.Result{
RequeueAfter: requeueAfter, RequeueAfter: requeueAfter,
@@ -414,21 +404,8 @@ func (r *ApplicationSetReconciler) setApplicationSetStatusCondition(ctx context.
paramtersGeneratedCondition := getParametersGeneratedCondition(paramtersGenerated, condition.Message) paramtersGeneratedCondition := getParametersGeneratedCondition(paramtersGenerated, condition.Message)
resourceUpToDateCondition := getResourceUpToDateCondition(errOccurred, condition.Message, condition.Reason) resourceUpToDateCondition := getResourceUpToDateCondition(errOccurred, condition.Message, condition.Reason)
evaluatedTypes := map[argov1alpha1.ApplicationSetConditionType]bool{
argov1alpha1.ApplicationSetConditionErrorOccurred: true,
argov1alpha1.ApplicationSetConditionParametersGenerated: true,
argov1alpha1.ApplicationSetConditionResourcesUpToDate: true,
}
newConditions := []argov1alpha1.ApplicationSetCondition{errOccurredCondition, paramtersGeneratedCondition, resourceUpToDateCondition} newConditions := []argov1alpha1.ApplicationSetCondition{errOccurredCondition, paramtersGeneratedCondition, resourceUpToDateCondition}
if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) {
evaluatedTypes[argov1alpha1.ApplicationSetConditionRolloutProgressing] = true
if condition.Type == argov1alpha1.ApplicationSetConditionRolloutProgressing {
newConditions = append(newConditions, condition)
}
}
needToUpdateConditions := false needToUpdateConditions := false
for _, condition := range newConditions { for _, condition := range newConditions {
// do nothing if appset already has same condition // do nothing if appset already has same condition
@@ -439,32 +416,28 @@ func (r *ApplicationSetReconciler) setApplicationSetStatusCondition(ctx context.
} }
} }
} }
evaluatedTypes := map[argov1alpha1.ApplicationSetConditionType]bool{
argov1alpha1.ApplicationSetConditionErrorOccurred: true,
argov1alpha1.ApplicationSetConditionParametersGenerated: true,
argov1alpha1.ApplicationSetConditionResourcesUpToDate: true,
}
if needToUpdateConditions || len(applicationSet.Status.Conditions) < len(newConditions) { if needToUpdateConditions || len(applicationSet.Status.Conditions) < 3 {
// fetch updated Application Set object before updating it // fetch updated Application Set object before updating it
// DefaultRetry will retry 5 times with a backoff factor of 1, jitter of 0.1 and a duration of 10ms namespacedName := types.NamespacedName{Namespace: applicationSet.Namespace, Name: applicationSet.Name}
err := retry.RetryOnConflict(retry.DefaultRetry, func() error { if err := r.Get(ctx, namespacedName, applicationSet); err != nil {
namespacedName := types.NamespacedName{Namespace: applicationSet.Namespace, Name: applicationSet.Name} if client.IgnoreNotFound(err) != nil {
updatedAppset := &argov1alpha1.ApplicationSet{} return nil
if err := r.Get(ctx, namespacedName, updatedAppset); err != nil {
if client.IgnoreNotFound(err) != nil {
return nil
}
return fmt.Errorf("error fetching updated application set: %w", err)
} }
return fmt.Errorf("error fetching updated application set: %w", err)
}
updatedAppset.Status.SetConditions( applicationSet.Status.SetConditions(
newConditions, evaluatedTypes, newConditions, evaluatedTypes,
) )
// Update the newly fetched object with new set of conditions // Update the newly fetched object with new set of conditions
err := r.Client.Status().Update(ctx, updatedAppset) err := r.Client.Status().Update(ctx, applicationSet)
if err != nil {
return err
}
updatedAppset.DeepCopyInto(applicationSet)
return nil
})
if err != nil && !apierr.IsNotFound(err) { if err != nil && !apierr.IsNotFound(err) {
return fmt.Errorf("unable to set application set condition: %w", err) return fmt.Errorf("unable to set application set condition: %w", err)
} }
@@ -485,9 +458,7 @@ func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Con
errorsByIndex[i] = fmt.Errorf("ApplicationSet %s contains applications with duplicate name: %s", applicationSetInfo.Name, app.Name) errorsByIndex[i] = fmt.Errorf("ApplicationSet %s contains applications with duplicate name: %s", applicationSetInfo.Name, app.Name)
continue continue
} }
_, err := r.ArgoAppClientset.ArgoprojV1alpha1().AppProjects(r.ArgoCDNamespace).Get(ctx, app.Spec.GetProject(), metav1.GetOptions{})
appProject := &argov1alpha1.AppProject{}
err := r.Client.Get(ctx, types.NamespacedName{Name: app.Spec.Project, Namespace: r.ArgoCDNamespace}, appProject)
if err != nil { if err != nil {
if apierr.IsNotFound(err) { if apierr.IsNotFound(err) {
errorsByIndex[i] = fmt.Errorf("application references project %s which does not exist", app.Spec.Project) errorsByIndex[i] = fmt.Errorf("application references project %s which does not exist", app.Spec.Project)
@@ -524,10 +495,92 @@ func (r *ApplicationSetReconciler) getMinRequeueAfter(applicationSetInfo *argov1
return res return res
} }
func getTempApplication(applicationSetTemplate argov1alpha1.ApplicationSetTemplate) *argov1alpha1.Application {
var tmplApplication argov1alpha1.Application
tmplApplication.Annotations = applicationSetTemplate.Annotations
tmplApplication.Labels = applicationSetTemplate.Labels
tmplApplication.Namespace = applicationSetTemplate.Namespace
tmplApplication.Name = applicationSetTemplate.Name
tmplApplication.Spec = applicationSetTemplate.Spec
tmplApplication.Finalizers = applicationSetTemplate.Finalizers
return &tmplApplication
}
func (r *ApplicationSetReconciler) generateApplications(logCtx *log.Entry, applicationSetInfo argov1alpha1.ApplicationSet) ([]argov1alpha1.Application, argov1alpha1.ApplicationSetReasonType, error) {
var res []argov1alpha1.Application
var firstError error
var applicationSetReason argov1alpha1.ApplicationSetReasonType
for _, requestedGenerator := range applicationSetInfo.Spec.Generators {
t, err := generators.Transform(requestedGenerator, r.Generators, applicationSetInfo.Spec.Template, &applicationSetInfo, map[string]interface{}{}, r.Client)
if err != nil {
logCtx.WithError(err).WithField("generator", requestedGenerator).
Error("error generating application from params")
if firstError == nil {
firstError = err
applicationSetReason = argov1alpha1.ApplicationSetReasonApplicationParamsGenerationError
}
continue
}
for _, a := range t {
tmplApplication := getTempApplication(a.Template)
for _, p := range a.Params {
app, err := r.Renderer.RenderTemplateParams(tmplApplication, applicationSetInfo.Spec.SyncPolicy, p, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
if err != nil {
logCtx.WithError(err).WithField("params", a.Params).WithField("generator", requestedGenerator).
Error("error generating application from params")
if firstError == nil {
firstError = err
applicationSetReason = argov1alpha1.ApplicationSetReasonRenderTemplateParamsError
}
continue
}
if applicationSetInfo.Spec.TemplatePatch != nil {
patchedApplication, err := r.applyTemplatePatch(app, applicationSetInfo, p)
if err != nil {
log.WithError(err).WithField("params", a.Params).WithField("generator", requestedGenerator).
Error("error generating application from params")
if firstError == nil {
firstError = err
applicationSetReason = argov1alpha1.ApplicationSetReasonRenderTemplateParamsError
}
continue
}
app = patchedApplication
}
res = append(res, *app)
}
}
logCtx.WithField("generator", requestedGenerator).Infof("generated %d applications", len(res))
logCtx.WithField("generator", requestedGenerator).Debugf("apps from generator: %+v", res)
}
return res, applicationSetReason, firstError
}
func (r *ApplicationSetReconciler) applyTemplatePatch(app *argov1alpha1.Application, applicationSetInfo argov1alpha1.ApplicationSet, params map[string]interface{}) (*argov1alpha1.Application, error) {
replacedTemplate, err := r.Renderer.Replace(*applicationSetInfo.Spec.TemplatePatch, params, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
if err != nil {
return nil, fmt.Errorf("error replacing values in templatePatch: %w", err)
}
return applyTemplatePatch(app, replacedTemplate)
}
func ignoreNotAllowedNamespaces(namespaces []string) predicate.Predicate { func ignoreNotAllowedNamespaces(namespaces []string) predicate.Predicate {
return predicate.Funcs{ return predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool { CreateFunc: func(e event.CreateEvent) bool {
return utils.IsNamespaceAllowed(namespaces, e.Object.GetNamespace()) return glob.MatchStringInList(namespaces, e.Object.GetNamespace(), false)
}, },
} }
} }
@@ -578,6 +631,10 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
var firstError error var firstError error
// Creates or updates the application in appList // Creates or updates the application in appList
for _, generatedApp := range desiredApplications { for _, generatedApp := range desiredApplications {
// The app's namespace must be the same as the AppSet's namespace to preserve the appsets-in-any-namespace
// security boundary.
generatedApp.Namespace = applicationSet.Namespace
appLog := logCtx.WithFields(log.Fields{"app": generatedApp.QualifiedName()}) appLog := logCtx.WithFields(log.Fields{"app": generatedApp.QualifiedName()})
// Normalize to avoid fighting with the application controller. // Normalize to avoid fighting with the application controller.
@@ -847,14 +904,14 @@ func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx conte
func (r *ApplicationSetReconciler) removeOwnerReferencesOnDeleteAppSet(ctx context.Context, applicationSet argov1alpha1.ApplicationSet) error { func (r *ApplicationSetReconciler) removeOwnerReferencesOnDeleteAppSet(ctx context.Context, applicationSet argov1alpha1.ApplicationSet) error {
applications, err := r.getCurrentApplications(ctx, applicationSet) applications, err := r.getCurrentApplications(ctx, applicationSet)
if err != nil { if err != nil {
return fmt.Errorf("error getting current applications for ApplicationSet: %w", err) return err
} }
for _, app := range applications { for _, app := range applications {
app.SetOwnerReferences([]metav1.OwnerReference{}) app.SetOwnerReferences([]metav1.OwnerReference{})
err := r.Client.Update(ctx, &app) err := r.Client.Update(ctx, &app)
if err != nil { if err != nil {
return fmt.Errorf("error updating application: %w", err) return err
} }
} }
@@ -862,9 +919,12 @@ func (r *ApplicationSetReconciler) removeOwnerReferencesOnDeleteAppSet(ctx conte
} }
func (r *ApplicationSetReconciler) performProgressiveSyncs(ctx context.Context, logCtx *log.Entry, appset argov1alpha1.ApplicationSet, applications []argov1alpha1.Application, desiredApplications []argov1alpha1.Application, appMap map[string]argov1alpha1.Application) (map[string]bool, error) { func (r *ApplicationSetReconciler) performProgressiveSyncs(ctx context.Context, logCtx *log.Entry, appset argov1alpha1.ApplicationSet, applications []argov1alpha1.Application, desiredApplications []argov1alpha1.Application, appMap map[string]argov1alpha1.Application) (map[string]bool, error) {
appDependencyList, appStepMap := r.buildAppDependencyList(logCtx, appset, desiredApplications) appDependencyList, appStepMap, err := r.buildAppDependencyList(logCtx, appset, desiredApplications)
if err != nil {
return nil, fmt.Errorf("failed to build app dependency list: %w", err)
}
_, err := r.updateApplicationSetApplicationStatus(ctx, logCtx, &appset, applications, appStepMap) _, err = r.updateApplicationSetApplicationStatus(ctx, logCtx, &appset, applications, appStepMap)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to update applicationset app status: %w", err) return nil, fmt.Errorf("failed to update applicationset app status: %w", err)
} }
@@ -874,27 +934,34 @@ func (r *ApplicationSetReconciler) performProgressiveSyncs(ctx context.Context,
logCtx.Infof("step %v: %+v", i+1, step) logCtx.Infof("step %v: %+v", i+1, step)
} }
appSyncMap := r.buildAppSyncMap(appset, appDependencyList, appMap) appSyncMap, err := r.buildAppSyncMap(ctx, appset, appDependencyList, appMap)
if err != nil {
return nil, fmt.Errorf("failed to build app sync map: %w", err)
}
logCtx.Infof("Application allowed to sync before maxUpdate?: %+v", appSyncMap) logCtx.Infof("Application allowed to sync before maxUpdate?: %+v", appSyncMap)
_, err = r.updateApplicationSetApplicationStatusProgress(ctx, logCtx, &appset, appSyncMap, appStepMap) _, err = r.updateApplicationSetApplicationStatusProgress(ctx, logCtx, &appset, appSyncMap, appStepMap, appMap)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to update applicationset application status progress: %w", err) return nil, fmt.Errorf("failed to update applicationset application status progress: %w", err)
} }
_ = r.updateApplicationSetApplicationStatusConditions(ctx, &appset) _, err = r.updateApplicationSetApplicationStatusConditions(ctx, &appset)
if err != nil {
return nil, fmt.Errorf("failed to update applicationset application status conditions: %w", err)
}
return appSyncMap, nil return appSyncMap, nil
} }
// this list tracks which Applications belong to each RollingUpdate step // this list tracks which Applications belong to each RollingUpdate step
func (r *ApplicationSetReconciler) buildAppDependencyList(logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, applications []argov1alpha1.Application) ([][]string, map[string]int) { func (r *ApplicationSetReconciler) buildAppDependencyList(logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, applications []argov1alpha1.Application) ([][]string, map[string]int, error) {
if applicationSet.Spec.Strategy == nil || applicationSet.Spec.Strategy.Type == "" || applicationSet.Spec.Strategy.Type == "AllAtOnce" { if applicationSet.Spec.Strategy == nil || applicationSet.Spec.Strategy.Type == "" || applicationSet.Spec.Strategy.Type == "AllAtOnce" {
return [][]string{}, map[string]int{} return [][]string{}, map[string]int{}, nil
} }
steps := []argov1alpha1.ApplicationSetRolloutStep{} steps := []argov1alpha1.ApplicationSetRolloutStep{}
if progressiveSyncsRollingSyncStrategyEnabled(&applicationSet) { if progressiveSyncsStrategyEnabled(&applicationSet, "RollingSync") {
steps = applicationSet.Spec.Strategy.RollingSync.Steps steps = applicationSet.Spec.Strategy.RollingSync.Steps
} }
@@ -935,7 +1002,7 @@ func (r *ApplicationSetReconciler) buildAppDependencyList(logCtx *log.Entry, app
} }
} }
return appDependencyList, appStepMap return appDependencyList, appStepMap, nil
} }
func labelMatchedExpression(logCtx *log.Entry, val string, matchExpression argov1alpha1.ApplicationMatchExpression) bool { func labelMatchedExpression(logCtx *log.Entry, val string, matchExpression argov1alpha1.ApplicationMatchExpression) bool {
@@ -959,7 +1026,7 @@ func labelMatchedExpression(logCtx *log.Entry, val string, matchExpression argov
} }
// this map is used to determine which stage of Applications are ready to be updated in the reconciler loop // this map is used to determine which stage of Applications are ready to be updated in the reconciler loop
func (r *ApplicationSetReconciler) buildAppSyncMap(applicationSet argov1alpha1.ApplicationSet, appDependencyList [][]string, appMap map[string]argov1alpha1.Application) map[string]bool { func (r *ApplicationSetReconciler) buildAppSyncMap(ctx context.Context, applicationSet argov1alpha1.ApplicationSet, appDependencyList [][]string, appMap map[string]argov1alpha1.Application) (map[string]bool, error) {
appSyncMap := map[string]bool{} appSyncMap := map[string]bool{}
syncEnabled := true syncEnabled := true
@@ -996,11 +1063,11 @@ func (r *ApplicationSetReconciler) buildAppSyncMap(applicationSet argov1alpha1.A
} }
} }
return appSyncMap return appSyncMap, nil
} }
func appSyncEnabledForNextStep(appset *argov1alpha1.ApplicationSet, app argov1alpha1.Application, appStatus argov1alpha1.ApplicationSetApplicationStatus) bool { func appSyncEnabledForNextStep(appset *argov1alpha1.ApplicationSet, app argov1alpha1.Application, appStatus argov1alpha1.ApplicationSetApplicationStatus) bool {
if progressiveSyncsRollingSyncStrategyEnabled(appset) { if progressiveSyncsStrategyEnabled(appset, "RollingSync") {
// we still need to complete the current step if the Application is not yet Healthy or there are still pending Application changes // we still need to complete the current step if the Application is not yet Healthy or there are still pending Application changes
return isApplicationHealthy(app) && appStatus.Status == "Healthy" return isApplicationHealthy(app) && appStatus.Status == "Healthy"
} }
@@ -1008,8 +1075,16 @@ func appSyncEnabledForNextStep(appset *argov1alpha1.ApplicationSet, app argov1al
return true return true
} }
func progressiveSyncsRollingSyncStrategyEnabled(appset *argov1alpha1.ApplicationSet) bool { func progressiveSyncsStrategyEnabled(appset *argov1alpha1.ApplicationSet, strategyType string) bool {
return appset.Spec.Strategy != nil && appset.Spec.Strategy.RollingSync != nil && appset.Spec.Strategy.Type == "RollingSync" && len(appset.Spec.Strategy.RollingSync.Steps) > 0 if appset.Spec.Strategy == nil || appset.Spec.Strategy.Type != strategyType {
return false
}
if strategyType == "RollingSync" && appset.Spec.Strategy.RollingSync == nil {
return false
}
return true
} }
func isApplicationHealthy(app argov1alpha1.Application) bool { func isApplicationHealthy(app argov1alpha1.Application) bool {
@@ -1032,16 +1107,6 @@ func statusStrings(app argov1alpha1.Application) (string, string, string) {
return healthStatusString, syncStatusString, operationPhaseString return healthStatusString, syncStatusString, operationPhaseString
} }
func getAppStep(appName string, appStepMap map[string]int) int {
// if an application is not selected by any match expression, it defaults to step -1
step := -1
if appStep, ok := appStepMap[appName]; ok {
// 1-based indexing
step = appStep + 1
}
return step
}
// check the status of each Application's status and promote Applications to the next status if needed // check the status of each Application's status and promote Applications to the next status if needed
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, applications []argov1alpha1.Application, appStepMap map[string]int) ([]argov1alpha1.ApplicationSetApplicationStatus, error) { func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, applications []argov1alpha1.Application, appStepMap map[string]int) ([]argov1alpha1.ApplicationSetApplicationStatus, error) {
now := metav1.Now() now := metav1.Now()
@@ -1061,7 +1126,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
LastTransitionTime: &now, LastTransitionTime: &now,
Message: "No Application status found, defaulting status to Waiting.", Message: "No Application status found, defaulting status to Waiting.",
Status: "Waiting", Status: "Waiting",
Step: strconv.Itoa(getAppStep(app.Name, appStepMap)), Step: fmt.Sprint(appStepMap[app.Name] + 1),
TargetRevisions: app.Status.GetRevisions(), TargetRevisions: app.Status.GetRevisions(),
} }
} else { } else {
@@ -1071,13 +1136,13 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
// upgrade any existing AppStatus that might have been set by an older argo-cd version // upgrade any existing AppStatus that might have been set by an older argo-cd version
// note: currentAppStatus.TargetRevisions may be set to empty list earlier during migrations, // note: currentAppStatus.TargetRevisions may be set to empty list earlier during migrations,
// to prevent other usage of r.Client.Status().Update to fail before reaching here. // to prevent other usage of r.Client.Status().Update to fail before reaching here.
if len(currentAppStatus.TargetRevisions) == 0 { if currentAppStatus.TargetRevisions == nil || len(currentAppStatus.TargetRevisions) == 0 {
currentAppStatus.TargetRevisions = app.Status.GetRevisions() currentAppStatus.TargetRevisions = app.Status.GetRevisions()
} }
} }
appOutdated := false appOutdated := false
if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) { if progressiveSyncsStrategyEnabled(applicationSet, "RollingSync") {
appOutdated = syncStatusString == "OutOfSync" appOutdated = syncStatusString == "OutOfSync"
} }
@@ -1086,7 +1151,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
currentAppStatus.LastTransitionTime = &now currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = "Waiting" currentAppStatus.Status = "Waiting"
currentAppStatus.Message = "Application has pending changes, setting status to Waiting." currentAppStatus.Message = "Application has pending changes, setting status to Waiting."
currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap)) currentAppStatus.Step = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
currentAppStatus.TargetRevisions = app.Status.GetRevisions() currentAppStatus.TargetRevisions = app.Status.GetRevisions()
} }
@@ -1104,14 +1169,14 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
currentAppStatus.LastTransitionTime = &now currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = "Progressing" currentAppStatus.Status = "Progressing"
currentAppStatus.Message = "Application resource completed a sync successfully, updating status from Pending to Progressing." currentAppStatus.Message = "Application resource completed a sync successfully, updating status from Pending to Progressing."
currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap)) currentAppStatus.Step = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
} }
} else if operationPhaseString == "Running" || healthStatusString == "Progressing" { } else if operationPhaseString == "Running" || healthStatusString == "Progressing" {
logCtx.Infof("Application %v has entered Progressing status, updating its ApplicationSet status to Progressing", app.Name) logCtx.Infof("Application %v has entered Progressing status, updating its ApplicationSet status to Progressing", app.Name)
currentAppStatus.LastTransitionTime = &now currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = "Progressing" currentAppStatus.Status = "Progressing"
currentAppStatus.Message = "Application resource became Progressing, updating status from Pending to Progressing." currentAppStatus.Message = "Application resource became Progressing, updating status from Pending to Progressing."
currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap)) currentAppStatus.Step = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
} }
} }
@@ -1120,7 +1185,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
currentAppStatus.LastTransitionTime = &now currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = healthStatusString currentAppStatus.Status = healthStatusString
currentAppStatus.Message = "Application resource is already Healthy, updating status from Waiting to Healthy." currentAppStatus.Message = "Application resource is already Healthy, updating status from Waiting to Healthy."
currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap)) currentAppStatus.Step = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
} }
if currentAppStatus.Status == "Progressing" && isApplicationHealthy(app) { if currentAppStatus.Status == "Progressing" && isApplicationHealthy(app) {
@@ -1128,7 +1193,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
currentAppStatus.LastTransitionTime = &now currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = healthStatusString currentAppStatus.Status = healthStatusString
currentAppStatus.Message = "Application resource became Healthy, updating status from Progressing to Healthy." currentAppStatus.Message = "Application resource became Healthy, updating status from Progressing to Healthy."
currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap)) currentAppStatus.Step = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
} }
appStatuses = append(appStatuses, currentAppStatus) appStatuses = append(appStatuses, currentAppStatus)
@@ -1143,18 +1208,20 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
} }
// check Applications that are in Waiting status and promote them to Pending if needed // check Applications that are in Waiting status and promote them to Pending if needed
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appStepMap map[string]int) ([]argov1alpha1.ApplicationSetApplicationStatus, error) { func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appStepMap map[string]int, appMap map[string]argov1alpha1.Application) ([]argov1alpha1.ApplicationSetApplicationStatus, error) {
now := metav1.Now() now := metav1.Now()
appStatuses := make([]argov1alpha1.ApplicationSetApplicationStatus, 0, len(applicationSet.Status.ApplicationStatus)) appStatuses := make([]argov1alpha1.ApplicationSetApplicationStatus, 0, len(applicationSet.Status.ApplicationStatus))
// if we have no RollingUpdate steps, clear out the existing ApplicationStatus entries // if we have no RollingUpdate steps, clear out the existing ApplicationStatus entries
if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) { if applicationSet.Spec.Strategy != nil && applicationSet.Spec.Strategy.Type != "" && applicationSet.Spec.Strategy.Type != "AllAtOnce" {
updateCountMap := []int{} updateCountMap := []int{}
totalCountMap := []int{} totalCountMap := []int{}
length := len(applicationSet.Spec.Strategy.RollingSync.Steps) length := 0
if progressiveSyncsStrategyEnabled(applicationSet, "RollingSync") {
length = len(applicationSet.Spec.Strategy.RollingSync.Steps)
}
for s := 0; s < length; s++ { for s := 0; s < length; s++ {
updateCountMap = append(updateCountMap, 0) updateCountMap = append(updateCountMap, 0)
totalCountMap = append(totalCountMap, 0) totalCountMap = append(totalCountMap, 0)
@@ -1164,15 +1231,17 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
for _, appStatus := range applicationSet.Status.ApplicationStatus { for _, appStatus := range applicationSet.Status.ApplicationStatus {
totalCountMap[appStepMap[appStatus.Application]] += 1 totalCountMap[appStepMap[appStatus.Application]] += 1
if appStatus.Status == "Pending" || appStatus.Status == "Progressing" { if progressiveSyncsStrategyEnabled(applicationSet, "RollingSync") {
updateCountMap[appStepMap[appStatus.Application]] += 1 if appStatus.Status == "Pending" || appStatus.Status == "Progressing" {
updateCountMap[appStepMap[appStatus.Application]] += 1
}
} }
} }
for _, appStatus := range applicationSet.Status.ApplicationStatus { for _, appStatus := range applicationSet.Status.ApplicationStatus {
maxUpdateAllowed := true maxUpdateAllowed := true
maxUpdate := &intstr.IntOrString{} maxUpdate := &intstr.IntOrString{}
if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) { if progressiveSyncsStrategyEnabled(applicationSet, "RollingSync") {
maxUpdate = applicationSet.Spec.Strategy.RollingSync.Steps[appStepMap[appStatus.Application]].MaxUpdate maxUpdate = applicationSet.Spec.Strategy.RollingSync.Steps[appStepMap[appStatus.Application]].MaxUpdate
} }
@@ -1190,7 +1259,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
if updateCountMap[appStepMap[appStatus.Application]] >= maxUpdateVal { if updateCountMap[appStepMap[appStatus.Application]] >= maxUpdateVal {
maxUpdateAllowed = false maxUpdateAllowed = false
logCtx.Infof("Application %v is not allowed to update yet, %v/%v Applications already updating in step %v in AppSet %v", appStatus.Application, updateCountMap[appStepMap[appStatus.Application]], maxUpdateVal, getAppStep(appStatus.Application, appStepMap), applicationSet.Name) logCtx.Infof("Application %v is not allowed to update yet, %v/%v Applications already updating in step %v in AppSet %v", appStatus.Application, updateCountMap[appStepMap[appStatus.Application]], maxUpdateVal, appStepMap[appStatus.Application]+1, applicationSet.Name)
} }
} }
@@ -1199,7 +1268,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
appStatus.LastTransitionTime = &now appStatus.LastTransitionTime = &now
appStatus.Status = "Pending" appStatus.Status = "Pending"
appStatus.Message = "Application moved to Pending status, watching for the Application resource to start Progressing." appStatus.Message = "Application moved to Pending status, watching for the Application resource to start Progressing."
appStatus.Step = strconv.Itoa(getAppStep(appStatus.Application, appStepMap)) appStatus.Step = fmt.Sprint(appStepMap[appStatus.Application] + 1)
updateCountMap[appStepMap[appStatus.Application]] += 1 updateCountMap[appStepMap[appStatus.Application]] += 1
} }
@@ -1216,7 +1285,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
return appStatuses, nil return appStatuses, nil
} }
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusConditions(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet) []argov1alpha1.ApplicationSetCondition { func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusConditions(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet) ([]argov1alpha1.ApplicationSetCondition, error) {
appSetProgressing := false appSetProgressing := false
for _, appStatus := range applicationSet.Status.ApplicationStatus { for _, appStatus := range applicationSet.Status.ApplicationStatus {
if appStatus.Status != "Healthy" { if appStatus.Status != "Healthy" {
@@ -1241,7 +1310,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusConditio
Message: "ApplicationSet Rollout Rollout started", Message: "ApplicationSet Rollout Rollout started",
Reason: argov1alpha1.ApplicationSetReasonApplicationSetModified, Reason: argov1alpha1.ApplicationSetReasonApplicationSetModified,
Status: argov1alpha1.ApplicationSetConditionStatusTrue, Status: argov1alpha1.ApplicationSetConditionStatusTrue,
}, true, }, false,
) )
} else if !appSetProgressing && appSetConditionProgressing { } else if !appSetProgressing && appSetConditionProgressing {
_ = r.setApplicationSetStatusCondition(ctx, _ = r.setApplicationSetStatusCondition(ctx,
@@ -1251,11 +1320,11 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusConditio
Message: "ApplicationSet Rollout Rollout complete", Message: "ApplicationSet Rollout Rollout complete",
Reason: argov1alpha1.ApplicationSetReasonApplicationSetRolloutComplete, Reason: argov1alpha1.ApplicationSetReasonApplicationSetRolloutComplete,
Status: argov1alpha1.ApplicationSetConditionStatusFalse, Status: argov1alpha1.ApplicationSetConditionStatusFalse,
}, true, }, false,
) )
} }
return applicationSet.Status.Conditions return applicationSet.Status.Conditions, nil
} }
func findApplicationStatusIndex(appStatuses []argov1alpha1.ApplicationSetApplicationStatus, application string) int { func findApplicationStatusIndex(appStatuses []argov1alpha1.ApplicationSetApplicationStatus, application string) int {
@@ -1281,75 +1350,93 @@ func (r *ApplicationSetReconciler) migrateStatus(ctx context.Context, appset *ar
} }
if update { if update {
// DefaultRetry will retry 5 times with a backoff factor of 1, jitter of 0.1 and a duration of 10ms if err := r.Client.Status().Update(ctx, appset); err != nil {
err := retry.RetryOnConflict(retry.DefaultRetry, func() error { return fmt.Errorf("unable to set application set status: %w", err)
namespacedName := types.NamespacedName{Namespace: appset.Namespace, Name: appset.Name}
updatedAppset := &argov1alpha1.ApplicationSet{}
if err := r.Get(ctx, namespacedName, updatedAppset); err != nil {
if client.IgnoreNotFound(err) != nil {
return nil
}
return fmt.Errorf("error fetching updated application set: %w", err)
}
updatedAppset.Status.ApplicationStatus = appset.Status.ApplicationStatus
// Update the newly fetched object with new set of ApplicationStatus
err := r.Client.Status().Update(ctx, updatedAppset)
if err != nil {
return err
}
updatedAppset.DeepCopyInto(appset)
return nil
})
if err != nil && !apierr.IsNotFound(err) {
return fmt.Errorf("unable to set application set condition: %w", err)
} }
} }
return nil return nil
} }
func (r *ApplicationSetReconciler) updateResourcesStatus(ctx context.Context, logCtx *log.Entry, appset *argov1alpha1.ApplicationSet, apps []argov1alpha1.Application) error { func (r *ApplicationSetReconciler) updateResourcesStatus(ctx context.Context, logCtx *log.Entry, appset *argov1alpha1.ApplicationSet, apps []argov1alpha1.Application) error {
statusMap := status.GetResourceStatusMap(appset) statusMap := getResourceStatusMap(appset)
statusMap = status.BuildResourceStatus(statusMap, apps) statusMap = buildResourceStatus(statusMap, apps)
statuses := []argov1alpha1.ResourceStatus{} statuses := []argov1alpha1.ResourceStatus{}
for _, status := range statusMap { for _, status := range statusMap {
statuses = append(statuses, status) statuses = append(statuses, status)
} }
sort.Slice(statuses, func(i, j int) bool {
return statuses[i].Name < statuses[j].Name
})
appset.Status.Resources = statuses appset.Status.Resources = statuses
// DefaultRetry will retry 5 times with a backoff factor of 1, jitter of 0.1 and a duration of 10ms
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
namespacedName := types.NamespacedName{Namespace: appset.Namespace, Name: appset.Name}
updatedAppset := &argov1alpha1.ApplicationSet{}
if err := r.Get(ctx, namespacedName, updatedAppset); err != nil {
if client.IgnoreNotFound(err) != nil {
return nil
}
return fmt.Errorf("error fetching updated application set: %w", err)
}
updatedAppset.Status.Resources = appset.Status.Resources namespacedName := types.NamespacedName{Namespace: appset.Namespace, Name: appset.Name}
err := r.Client.Status().Update(ctx, appset)
// Update the newly fetched object with new status resources
err := r.Client.Status().Update(ctx, updatedAppset)
if err != nil {
return err
}
updatedAppset.DeepCopyInto(appset)
return nil
})
if err != nil { if err != nil {
logCtx.Errorf("unable to set application set status: %v", err) logCtx.Errorf("unable to set application set status: %v", err)
return fmt.Errorf("unable to set application set status: %w", err) return fmt.Errorf("unable to set application set status: %w", err)
} }
if err := r.Get(ctx, namespacedName, appset); err != nil {
if client.IgnoreNotFound(err) != nil {
return nil
}
return fmt.Errorf("error fetching updated application set: %w", err)
}
return nil return nil
} }
// setAppSetApplicationStatus updates the ApplicationSet's status field func buildResourceStatus(statusMap map[string]argov1alpha1.ResourceStatus, apps []argov1alpha1.Application) map[string]argov1alpha1.ResourceStatus {
appMap := map[string]argov1alpha1.Application{}
for _, app := range apps {
appCopy := app
appMap[app.Name] = app
gvk := app.GroupVersionKind()
// Create status if it does not exist
status, ok := statusMap[app.Name]
if !ok {
status = argov1alpha1.ResourceStatus{
Group: gvk.Group,
Version: gvk.Version,
Kind: gvk.Kind,
Name: app.Name,
Namespace: app.Namespace,
Status: app.Status.Sync.Status,
Health: &appCopy.Status.Health,
}
}
status.Group = gvk.Group
status.Version = gvk.Version
status.Kind = gvk.Kind
status.Name = app.Name
status.Namespace = app.Namespace
status.Status = app.Status.Sync.Status
status.Health = &appCopy.Status.Health
statusMap[app.Name] = status
}
cleanupDeletedApplicationStatuses(statusMap, appMap)
return statusMap
}
func getResourceStatusMap(appset *argov1alpha1.ApplicationSet) map[string]argov1alpha1.ResourceStatus {
statusMap := map[string]argov1alpha1.ResourceStatus{}
for _, status := range appset.Status.Resources {
statusMap[status.Name] = status
}
return statusMap
}
func cleanupDeletedApplicationStatuses(statusMap map[string]argov1alpha1.ResourceStatus, apps map[string]argov1alpha1.Application) {
for name := range statusMap {
if _, ok := apps[name]; !ok {
delete(statusMap, name)
}
}
}
// setApplicationSetApplicationStatus updates the ApplicationSet's status field
// with any new/changed Application statuses. // with any new/changed Application statuses.
func (r *ApplicationSetReconciler) setAppSetApplicationStatus(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, applicationStatuses []argov1alpha1.ApplicationSetApplicationStatus) error { func (r *ApplicationSetReconciler) setAppSetApplicationStatus(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, applicationStatuses []argov1alpha1.ApplicationSetApplicationStatus) error {
needToUpdateStatus := false needToUpdateStatus := false
@@ -1380,36 +1467,26 @@ func (r *ApplicationSetReconciler) setAppSetApplicationStatus(ctx context.Contex
for i := range applicationStatuses { for i := range applicationStatuses {
applicationSet.Status.SetApplicationStatus(applicationStatuses[i]) applicationSet.Status.SetApplicationStatus(applicationStatuses[i])
} }
// DefaultRetry will retry 5 times with a backoff factor of 1, jitter of 0.1 and a duration of 10ms
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
updatedAppset := &argov1alpha1.ApplicationSet{}
if err := r.Get(ctx, namespacedName, updatedAppset); err != nil {
if client.IgnoreNotFound(err) != nil {
return nil
}
return fmt.Errorf("error fetching updated application set: %w", err)
}
updatedAppset.Status.ApplicationStatus = applicationSet.Status.ApplicationStatus // Update the newly fetched object with new set of ApplicationStatus
err := r.Client.Status().Update(ctx, applicationSet)
// Update the newly fetched object with new set of ApplicationStatus
err := r.Client.Status().Update(ctx, updatedAppset)
if err != nil {
return err
}
updatedAppset.DeepCopyInto(applicationSet)
return nil
})
if err != nil { if err != nil {
logCtx.Errorf("unable to set application set status: %v", err) logCtx.Errorf("unable to set application set status: %v", err)
return fmt.Errorf("unable to set application set status: %w", err) return fmt.Errorf("unable to set application set status: %w", err)
} }
if err := r.Get(ctx, namespacedName, applicationSet); err != nil {
if client.IgnoreNotFound(err) != nil {
return nil
}
return fmt.Errorf("error fetching updated application set: %w", err)
}
} }
return nil return nil
} }
func (r *ApplicationSetReconciler) syncValidApplications(logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appMap map[string]argov1alpha1.Application, validApps []argov1alpha1.Application) []argov1alpha1.Application { func (r *ApplicationSetReconciler) syncValidApplications(logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appMap map[string]argov1alpha1.Application, validApps []argov1alpha1.Application) ([]argov1alpha1.Application, error) {
rolloutApps := []argov1alpha1.Application{} rolloutApps := []argov1alpha1.Application{}
for i := range validApps { for i := range validApps {
pruneEnabled := false pruneEnabled := false
@@ -1430,15 +1507,15 @@ func (r *ApplicationSetReconciler) syncValidApplications(logCtx *log.Entry, appl
// check appSyncMap to determine which Applications are ready to be updated and which should be skipped // check appSyncMap to determine which Applications are ready to be updated and which should be skipped
if appSyncMap[validApps[i].Name] && appMap[validApps[i].Name].Status.Sync.Status == "OutOfSync" && appSetStatusPending { if appSyncMap[validApps[i].Name] && appMap[validApps[i].Name].Status.Sync.Status == "OutOfSync" && appSetStatusPending {
logCtx.Infof("triggering sync for application: %v, prune enabled: %v", validApps[i].Name, pruneEnabled) logCtx.Infof("triggering sync for application: %v, prune enabled: %v", validApps[i].Name, pruneEnabled)
validApps[i] = syncApplication(validApps[i], pruneEnabled) validApps[i], _ = syncApplication(validApps[i], pruneEnabled)
} }
rolloutApps = append(rolloutApps, validApps[i]) rolloutApps = append(rolloutApps, validApps[i])
} }
return rolloutApps return rolloutApps, nil
} }
// used by the RollingSync Progressive Sync strategy to trigger a sync of a particular Application resource // used by the RollingSync Progressive Sync strategy to trigger a sync of a particular Application resource
func syncApplication(application argov1alpha1.Application, prune bool) argov1alpha1.Application { func syncApplication(application argov1alpha1.Application, prune bool) (argov1alpha1.Application, error) {
operation := argov1alpha1.Operation{ operation := argov1alpha1.Operation{
InitiatedBy: argov1alpha1.OperationInitiator{ InitiatedBy: argov1alpha1.OperationInitiator{
Username: "applicationset-controller", Username: "applicationset-controller",
@@ -1464,7 +1541,7 @@ func syncApplication(application argov1alpha1.Application, prune bool) argov1alp
} }
application.Operation = &operation application.Operation = &operation
return application return application, nil
} }
func getOwnsHandlerPredicates(enableProgressiveSyncs bool) predicate.Funcs { func getOwnsHandlerPredicates(enableProgressiveSyncs bool) predicate.Funcs {
@@ -1505,7 +1582,7 @@ func getOwnsHandlerPredicates(enableProgressiveSyncs bool) predicate.Funcs {
return false return false
} }
requeue := shouldRequeueApplicationSet(appOld, appNew, enableProgressiveSyncs) requeue := shouldRequeueApplicationSet(appOld, appNew, enableProgressiveSyncs)
logCtx.WithField("requeue", requeue).Debugf("requeue: %t caused by application %s", requeue, appNew.Name) logCtx.WithField("requeue", requeue).Debugf("requeue: %t caused by application %s\n", requeue, appNew.Name)
return requeue return requeue
}, },
GenericFunc: func(e event.GenericEvent) bool { GenericFunc: func(e event.GenericEvent) bool {

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
@@ -14,7 +12,7 @@ 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/v2/common" "github.com/argoproj/argo-cd/v2/applicationset/generators"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
@@ -26,31 +24,31 @@ type clusterSecretEventHandler struct {
Client client.Client Client client.Client
} }
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.RateLimitingInterface) {
h.queueRelatedAppGenerators(ctx, q, e.Object) h.queueRelatedAppGenerators(ctx, q, e.Object)
} }
func (h *clusterSecretEventHandler) Update(ctx context.Context, e event.UpdateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { func (h *clusterSecretEventHandler) Update(ctx context.Context, e event.UpdateEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(ctx, q, e.ObjectNew) h.queueRelatedAppGenerators(ctx, q, e.ObjectNew)
} }
func (h *clusterSecretEventHandler) Delete(ctx context.Context, e event.DeleteEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { func (h *clusterSecretEventHandler) Delete(ctx context.Context, e event.DeleteEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(ctx, q, e.Object) h.queueRelatedAppGenerators(ctx, q, e.Object)
} }
func (h *clusterSecretEventHandler) Generic(ctx context.Context, e event.GenericEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { func (h *clusterSecretEventHandler) Generic(ctx context.Context, e event.GenericEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(ctx, q, e.Object) h.queueRelatedAppGenerators(ctx, q, e.Object)
} }
// addRateLimitingInterface defines the Add method of workqueue.RateLimitingInterface, allow us to easily mock // addRateLimitingInterface defines the Add method of workqueue.RateLimitingInterface, allow us to easily mock
// it for testing purposes. // it for testing purposes.
type addRateLimitingInterface[T comparable] interface { type addRateLimitingInterface interface {
Add(item T) Add(item interface{})
} }
func (h *clusterSecretEventHandler) queueRelatedAppGenerators(ctx context.Context, q addRateLimitingInterface[reconcile.Request], object client.Object) { func (h *clusterSecretEventHandler) queueRelatedAppGenerators(ctx context.Context, q addRateLimitingInterface, object client.Object) {
// Check for label, lookup all ApplicationSets that might match the cluster, queue them all // Check for label, lookup all ApplicationSets that might match the cluster, queue them all
if object.GetLabels()[common.LabelKeySecretType] != common.LabelValueSecretTypeCluster { if object.GetLabels()[generators.ArgoCDSecretTypeLabel] != generators.ArgoCDSecretTypeCluster {
return return
} }

View File

@@ -4,8 +4,6 @@ import (
"context" "context"
"testing" "testing"
argocommon "github.com/argoproj/argo-cd/v2/common"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -18,6 +16,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/argoproj/argo-cd/v2/applicationset/generators"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
@@ -43,7 +42,7 @@ func TestClusterEventHandler(t *testing.T) {
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
}, },
}, },
}, },
@@ -71,7 +70,7 @@ func TestClusterEventHandler(t *testing.T) {
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
}, },
}, },
}, },
@@ -114,7 +113,7 @@ func TestClusterEventHandler(t *testing.T) {
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
}, },
}, },
}, },
@@ -158,7 +157,7 @@ func TestClusterEventHandler(t *testing.T) {
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
}, },
}, },
}, },
@@ -219,7 +218,7 @@ func TestClusterEventHandler(t *testing.T) {
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
}, },
}, },
}, },
@@ -255,7 +254,7 @@ func TestClusterEventHandler(t *testing.T) {
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
}, },
}, },
}, },
@@ -305,7 +304,7 @@ func TestClusterEventHandler(t *testing.T) {
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
}, },
}, },
}, },
@@ -356,7 +355,7 @@ func TestClusterEventHandler(t *testing.T) {
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
}, },
}, },
}, },
@@ -390,7 +389,7 @@ func TestClusterEventHandler(t *testing.T) {
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
}, },
}, },
}, },
@@ -426,7 +425,7 @@ func TestClusterEventHandler(t *testing.T) {
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
}, },
}, },
}, },
@@ -476,7 +475,7 @@ func TestClusterEventHandler(t *testing.T) {
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
}, },
}, },
}, },
@@ -527,7 +526,7 @@ func TestClusterEventHandler(t *testing.T) {
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
}, },
}, },
}, },
@@ -552,18 +551,24 @@ func TestClusterEventHandler(t *testing.T) {
handler.queueRelatedAppGenerators(context.Background(), &mockAddRateLimitingInterface, &test.secret) handler.queueRelatedAppGenerators(context.Background(), &mockAddRateLimitingInterface, &test.secret)
assert.False(t, mockAddRateLimitingInterface.errorOccurred)
assert.ElementsMatch(t, mockAddRateLimitingInterface.addedItems, test.expectedRequests) assert.ElementsMatch(t, mockAddRateLimitingInterface.addedItems, test.expectedRequests)
}) })
} }
} }
// Add checks the type, and adds it to the internal list of received additions // Add checks the type, and adds it to the internal list of received additions
func (obj *mockAddRateLimitingInterface) Add(item reconcile.Request) { func (obj *mockAddRateLimitingInterface) Add(item interface{}) {
obj.addedItems = append(obj.addedItems, item) if req, ok := item.(ctrl.Request); ok {
obj.addedItems = append(obj.addedItems, req)
} else {
obj.errorOccurred = true
}
} }
type mockAddRateLimitingInterface struct { type mockAddRateLimitingInterface struct {
addedItems []reconcile.Request errorOccurred bool
addedItems []ctrl.Request
} }
func TestNestedGeneratorHasClusterGenerator_NestedClusterGenerator(t *testing.T) { func TestNestedGeneratorHasClusterGenerator_NestedClusterGenerator(t *testing.T) {

View File

@@ -17,7 +17,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v2/applicationset/generators" "github.com/argoproj/argo-cd/v2/applicationset/generators"
appsetmetrics "github.com/argoproj/argo-cd/v2/applicationset/metrics"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks" "github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
@@ -57,14 +56,14 @@ func TestRequeueAfter(t *testing.T) {
}, },
} }
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, duckType) fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, duckType)
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(k8sClient, ctx, appClientset, "argocd"), "Clusters": generators.NewClusterGenerator(k8sClient, ctx, appClientset, "argocd"),
"Git": generators.NewGitGenerator(mockServer, "namespace"), "Git": generators.NewGitGenerator(mockServer, "namespace"),
"SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), scmConfig), "SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), generators.SCMAuthProviders{}, "", []string{""}, true),
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, fakeDynClient, appClientset, "argocd"), "ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, fakeDynClient, appClientset, "argocd"),
"PullRequest": generators.NewPullRequestGenerator(k8sClient, scmConfig), "PullRequest": generators.NewPullRequestGenerator(k8sClient, generators.SCMAuthProviders{}, "", []string{""}, true),
} }
nestedGenerators := map[string]generators.Generator{ nestedGenerators := map[string]generators.Generator{
@@ -90,18 +89,15 @@ func TestRequeueAfter(t *testing.T) {
} }
client := fake.NewClientBuilder().WithScheme(scheme).Build() client := fake.NewClientBuilder().WithScheme(scheme).Build()
metrics := appsetmetrics.NewFakeAppsetMetrics(client)
r := ApplicationSetReconciler{ r := ApplicationSetReconciler{
Client: client, Client: client,
Scheme: scheme, Scheme: scheme,
Recorder: record.NewFakeRecorder(0), Recorder: record.NewFakeRecorder(0),
Generators: topLevelGenerators, Generators: topLevelGenerators,
Metrics: metrics,
} }
type args struct { type args struct {
appset *argov1alpha1.ApplicationSet appset *argov1alpha1.ApplicationSet
requeueAfterOverride string
} }
tests := []struct { tests := []struct {
name string name string
@@ -109,13 +105,11 @@ func TestRequeueAfter(t *testing.T) {
want time.Duration want time.Duration
wantErr assert.ErrorAssertionFunc wantErr assert.ErrorAssertionFunc
}{ }{
{name: "Cluster", args: args{ {name: "Cluster", args: args{appset: &argov1alpha1.ApplicationSet{
appset: &argov1alpha1.ApplicationSet{ Spec: argov1alpha1.ApplicationSetSpec{
Spec: argov1alpha1.ApplicationSetSpec{ Generators: []argov1alpha1.ApplicationSetGenerator{{Clusters: &argov1alpha1.ClusterGenerator{}}},
Generators: []argov1alpha1.ApplicationSetGenerator{{Clusters: &argov1alpha1.ClusterGenerator{}}}, },
}, }}, want: generators.NoRequeueAfter, wantErr: assert.NoError},
}, requeueAfterOverride: "",
}, want: generators.NoRequeueAfter, wantErr: assert.NoError},
{name: "ClusterMergeNested", args: args{&argov1alpha1.ApplicationSet{ {name: "ClusterMergeNested", args: args{&argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{ Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{ Generators: []argov1alpha1.ApplicationSetGenerator{
@@ -130,7 +124,7 @@ func TestRequeueAfter(t *testing.T) {
}}, }},
}, },
}, },
}, ""}, want: generators.DefaultRequeueAfterSeconds, 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{
@@ -145,65 +139,15 @@ func TestRequeueAfter(t *testing.T) {
}}, }},
}, },
}, },
}, ""}, want: generators.DefaultRequeueAfterSeconds, 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{}}},
}, },
}}, want: generators.NoRequeueAfter, wantErr: assert.NoError}, }}, want: generators.NoRequeueAfter, wantErr: assert.NoError},
{name: "DuckGenerator", args: args{appset: &argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{{ClusterDecisionResource: &argov1alpha1.DuckTypeGenerator{}}},
},
}}, want: generators.DefaultRequeueAfterSeconds, wantErr: assert.NoError},
{name: "OverrideRequeueDuck", args: args{
appset: &argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{{ClusterDecisionResource: &argov1alpha1.DuckTypeGenerator{}}},
},
}, requeueAfterOverride: "1h",
}, want: 1 * time.Hour, wantErr: assert.NoError},
{name: "OverrideRequeueGit", args: args{&argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{
{Git: &argov1alpha1.GitGenerator{}},
},
},
}, "1h"}, want: 1 * time.Hour, wantErr: assert.NoError},
{name: "OverrideRequeueMatrix", args: args{&argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{
{Clusters: &argov1alpha1.ClusterGenerator{}},
{Merge: &argov1alpha1.MergeGenerator{
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
{
Clusters: &argov1alpha1.ClusterGenerator{},
Git: &argov1alpha1.GitGenerator{},
},
},
}},
},
},
}, "5m"}, want: 5 * time.Minute, wantErr: assert.NoError},
{name: "OverrideRequeueMerge", args: args{&argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{
{Clusters: &argov1alpha1.ClusterGenerator{}},
{Merge: &argov1alpha1.MergeGenerator{
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
{
Clusters: &argov1alpha1.ClusterGenerator{},
Git: &argov1alpha1.GitGenerator{},
},
},
}},
},
},
}, "12s"}, want: 12 * time.Second, wantErr: assert.NoError},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
t.Setenv("ARGOCD_APPLICATIONSET_CONTROLLER_REQUEUE_AFTER", tt.args.requeueAfterOverride)
assert.Equalf(t, tt.want, r.getMinRequeueAfter(tt.args.appset), "getMinRequeueAfter(%v)", tt.args.appset) assert.Equalf(t, tt.want, r.getMinRequeueAfter(tt.args.appset), "getMinRequeueAfter(%v)", tt.args.appset)
}) })
} }

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package template package controllers
import ( import (
"encoding/json" "encoding/json"

View File

@@ -1,4 +1,4 @@
package template package controllers
import ( import (
"testing" "testing"

View File

@@ -15,10 +15,14 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v2/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/argoproj/argo-cd/v2/common"
argoappsetv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoappsetv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
const (
ArgoCDSecretTypeLabel = "argocd.argoproj.io/secret-type"
ArgoCDSecretTypeCluster = "cluster"
)
var _ Generator = (*ClusterGenerator)(nil) var _ Generator = (*ClusterGenerator)(nil)
// ClusterGenerator generates Applications for some or all clusters registered with ArgoCD. // ClusterGenerator generates Applications for some or all clusters registered with ArgoCD.
@@ -48,7 +52,7 @@ func NewClusterGenerator(c client.Client, ctx context.Context, clientset kuberne
// GetRequeueAfter never requeue the cluster generator because the `clusterSecretEventHandler` will requeue the appsets // GetRequeueAfter never requeue the cluster generator because the `clusterSecretEventHandler` will requeue the appsets
// when the cluster secrets change // when the cluster secrets change
func (g *ClusterGenerator) GetRequeueAfter(_ *argoappsetv1alpha1.ApplicationSetGenerator) time.Duration { func (g *ClusterGenerator) GetRequeueAfter(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) time.Duration {
return NoRequeueAfter return NoRequeueAfter
} }
@@ -57,7 +61,6 @@ func (g *ClusterGenerator) GetTemplate(appSetGenerator *argoappsetv1alpha1.Appli
} }
func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) { func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
logCtx := log.WithField("applicationset", appSet.GetName()).WithField("namespace", appSet.GetNamespace())
if appSetGenerator == nil { if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError return nil, EmptyAppSetGeneratorError
} }
@@ -80,19 +83,15 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
return nil, nil return nil, nil
} }
clusterSecrets, err := g.getSecretsByClusterName(logCtx, appSetGenerator) clusterSecrets, err := g.getSecretsByClusterName(appSetGenerator)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting cluster secrets: %w", err) return nil, err
} }
res := []map[string]interface{}{} res := []map[string]interface{}{}
secretsFound := []corev1.Secret{} secretsFound := []corev1.Secret{}
isFlatMode := appSetGenerator.Clusters.FlatList
logCtx.Debugf("Using flat mode = %t for cluster generator", isFlatMode)
clustersParams := make([]map[string]interface{}, 0)
for _, cluster := range clustersFromArgoCD.Items { for _, cluster := range clustersFromArgoCD.Items {
// If there is a secret for this cluster, then it's a non-local cluster, so it will be // If there is a secret for this cluster, then it's a non-local cluster, so it will be
// handled by the next step. // handled by the next step.
@@ -104,20 +103,15 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
params["name"] = cluster.Name params["name"] = cluster.Name
params["nameNormalized"] = cluster.Name params["nameNormalized"] = cluster.Name
params["server"] = cluster.Server params["server"] = cluster.Server
params["project"] = ""
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 local cluster: %w", err) return nil, err
} }
if isFlatMode { res = append(res, params)
clustersParams = append(clustersParams, params)
} else {
res = append(res, params)
}
logCtx.WithField("cluster", "local cluster").Info("matched local cluster") log.WithField("cluster", "local cluster").Info("matched local cluster")
} }
} }
@@ -129,13 +123,6 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
params["nameNormalized"] = utils.SanitizeName(string(cluster.Data["name"])) params["nameNormalized"] = utils.SanitizeName(string(cluster.Data["name"]))
params["server"] = string(cluster.Data["server"]) params["server"] = string(cluster.Data["server"])
project, ok := cluster.Data["project"]
if ok {
params["project"] = string(project)
} else {
params["project"] = ""
}
if appSet.Spec.GoTemplate { if appSet.Spec.GoTemplate {
meta := map[string]interface{}{} meta := map[string]interface{}{}
@@ -159,39 +146,31 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions) err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet.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, err
} }
if isFlatMode { res = append(res, params)
clustersParams = append(clustersParams, params)
} else {
res = append(res, params)
}
logCtx.WithField("cluster", cluster.Name).Debug("matched cluster secret") log.WithField("cluster", cluster.Name).Info("matched cluster secret")
} }
if isFlatMode {
res = append(res, map[string]interface{}{
"clusters": clustersParams,
})
}
return res, nil return res, nil
} }
func (g *ClusterGenerator) getSecretsByClusterName(log *log.Entry, appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) (map[string]corev1.Secret, error) { func (g *ClusterGenerator) getSecretsByClusterName(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) (map[string]corev1.Secret, error) {
// List all Clusters:
clusterSecretList := &corev1.SecretList{} clusterSecretList := &corev1.SecretList{}
selector := metav1.AddLabelToSelector(&appSetGenerator.Clusters.Selector, common.LabelKeySecretType, common.LabelValueSecretTypeCluster) selector := metav1.AddLabelToSelector(&appSetGenerator.Clusters.Selector, ArgoCDSecretTypeLabel, ArgoCDSecretTypeCluster)
secretSelector, err := metav1.LabelSelectorAsSelector(selector) secretSelector, err := metav1.LabelSelectorAsSelector(selector)
if err != nil { if err != nil {
return nil, fmt.Errorf("error converting label selector: %w", err) return nil, err
} }
if err := g.Client.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.Debug("clusters matching labels", "count", len(clusterSecretList.Items))
res := map[string]corev1.Secret{} res := map[string]corev1.Secret{}

View File

@@ -76,20 +76,18 @@ func TestGenerateParams(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"config": []byte("{}"), "config": []byte("{}"),
"name": []byte("production_01/west"), "name": []byte("production_01/west"),
"server": []byte("https://production-01.example.com"), "server": []byte("https://production-01.example.com"),
"project": []byte("prod-project"),
}, },
Type: corev1.SecretType("Opaque"), Type: corev1.SecretType("Opaque"),
}, },
} }
testCases := []struct { testCases := []struct {
name string name string
selector metav1.LabelSelector selector metav1.LabelSelector
isFlatMode bool values map[string]string
values map[string]string expected []map[string]interface{}
expected []map[string]interface{}
// clientError is true if a k8s client error should be simulated // clientError is true if a k8s client error should be simulated
clientError bool clientError bool
expectedError error expectedError error
@@ -107,16 +105,17 @@ func TestGenerateParams(t *testing.T) {
"aaa": "{{ server }}", "aaa": "{{ server }}",
"no-op": "{{ this-does-not-exist }}", "no-op": "{{ this-does-not-exist }}",
}, expected: []map[string]interface{}{ }, expected: []map[string]interface{}{
{"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "{{ metadata.annotations.foo.argoproj.io }}", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "{{ metadata.labels.environment }}", "values.aaa": "https://kubernetes.default.svc", "nameNormalized": "in-cluster", "name": "in-cluster", "server": "https://kubernetes.default.svc", "project": ""},
{ {
"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "production", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "production", "values.aaa": "https://production-01.example.com", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar", "values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "production", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "production", "values.aaa": "https://production-01.example.com", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production",
}, },
{ {
"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "staging", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "staging", "values.aaa": "https://staging-01.example.com", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo", "values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "staging", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "staging", "values.aaa": "https://staging-01.example.com", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging",
}, },
{"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "{{ metadata.annotations.foo.argoproj.io }}", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "{{ metadata.labels.environment }}", "values.aaa": "https://kubernetes.default.svc", "nameNormalized": "in-cluster", "name": "in-cluster", "server": "https://kubernetes.default.svc"},
}, },
clientError: false, clientError: false,
expectedError: nil, expectedError: nil,
@@ -132,12 +131,12 @@ func TestGenerateParams(t *testing.T) {
expected: []map[string]interface{}{ expected: []map[string]interface{}{
{ {
"name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production",
}, },
{ {
"name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging",
}, },
}, },
clientError: false, clientError: false,
@@ -156,7 +155,7 @@ func TestGenerateParams(t *testing.T) {
expected: []map[string]interface{}{ expected: []map[string]interface{}{
{ {
"values.foo": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar", "values.foo": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production",
}, },
}, },
clientError: false, clientError: false,
@@ -182,11 +181,11 @@ func TestGenerateParams(t *testing.T) {
expected: []map[string]interface{}{ expected: []map[string]interface{}{
{ {
"values.foo": "bar", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo", "values.foo": "bar", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging",
}, },
{ {
"values.foo": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar", "values.foo": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production",
}, },
}, },
clientError: false, clientError: false,
@@ -215,7 +214,7 @@ func TestGenerateParams(t *testing.T) {
expected: []map[string]interface{}{ expected: []map[string]interface{}{
{ {
"values.name": "baz", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo", "values.name": "baz", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging",
}, },
}, },
clientError: false, clientError: false,
@@ -227,75 +226,7 @@ func TestGenerateParams(t *testing.T) {
values: nil, values: nil,
expected: nil, expected: nil,
clientError: true, clientError: true,
expectedError: fmt.Errorf("error getting cluster secrets: could not list Secrets"), expectedError: fmt.Errorf("could not list Secrets"),
},
{
name: "flat mode without selectors",
selector: metav1.LabelSelector{},
values: map[string]string{
"lol1": "lol",
"lol2": "{{values.lol1}}{{values.lol1}}",
"lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}",
"foo": "bar",
"bar": "{{ metadata.annotations.foo.argoproj.io }}",
"bat": "{{ metadata.labels.environment }}",
"aaa": "{{ server }}",
"no-op": "{{ this-does-not-exist }}",
},
expected: []map[string]interface{}{
{
"clusters": []map[string]interface{}{
{"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "{{ metadata.annotations.foo.argoproj.io }}", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "{{ metadata.labels.environment }}", "values.aaa": "https://kubernetes.default.svc", "nameNormalized": "in-cluster", "name": "in-cluster", "server": "https://kubernetes.default.svc", "project": ""},
{
"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "production", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "production", "values.aaa": "https://production-01.example.com", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
},
{
"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "staging", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "staging", "values.aaa": "https://staging-01.example.com", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
},
},
},
},
isFlatMode: true,
clientError: false,
expectedError: nil,
},
{
name: "production or staging with flat mode",
selector: metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "environment",
Operator: "In",
Values: []string{
"production",
"staging",
},
},
},
},
isFlatMode: true,
values: map[string]string{
"foo": "bar",
},
expected: []map[string]interface{}{
{
"clusters": []map[string]interface{}{
{
"values.foo": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
},
{
"values.foo": "bar", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
},
},
},
},
clientError: false,
expectedError: nil,
}, },
} }
@@ -328,7 +259,6 @@ func TestGenerateParams(t *testing.T) {
Clusters: &argoprojiov1alpha1.ClusterGenerator{ Clusters: &argoprojiov1alpha1.ClusterGenerator{
Selector: testCase.selector, Selector: testCase.selector,
Values: testCase.values, Values: testCase.values,
FlatList: testCase.isFlatMode,
}, },
}, &applicationSetInfo, nil) }, &applicationSetInfo, nil)
@@ -394,11 +324,10 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
}, },
} }
testCases := []struct { testCases := []struct {
name string name string
selector metav1.LabelSelector selector metav1.LabelSelector
values map[string]string values map[string]string
isFlatMode bool expected []map[string]interface{}
expected []map[string]interface{}
// clientError is true if a k8s client error should be simulated // clientError is true if a k8s client error should be simulated
clientError bool clientError bool
expectedError error expectedError error
@@ -420,7 +349,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "production_01/west", "name": "production_01/west",
"nameNormalized": "production-01-west", "nameNormalized": "production-01-west",
"server": "https://production-01.example.com", "server": "https://production-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -446,7 +374,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "staging-01", "name": "staging-01",
"nameNormalized": "staging-01", "nameNormalized": "staging-01",
"server": "https://staging-01.example.com", "server": "https://staging-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -472,7 +399,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"nameNormalized": "in-cluster", "nameNormalized": "in-cluster",
"name": "in-cluster", "name": "in-cluster",
"server": "https://kubernetes.default.svc", "server": "https://kubernetes.default.svc",
"project": "",
"values": map[string]string{ "values": map[string]string{
"lol1": "lol", "lol1": "lol",
"lol2": "<no value><no value>", "lol2": "<no value><no value>",
@@ -501,7 +427,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "production_01/west", "name": "production_01/west",
"nameNormalized": "production-01-west", "nameNormalized": "production-01-west",
"server": "https://production-01.example.com", "server": "https://production-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -517,7 +442,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "staging-01", "name": "staging-01",
"nameNormalized": "staging-01", "nameNormalized": "staging-01",
"server": "https://staging-01.example.com", "server": "https://staging-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -548,7 +472,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "production_01/west", "name": "production_01/west",
"nameNormalized": "production-01-west", "nameNormalized": "production-01-west",
"server": "https://production-01.example.com", "server": "https://production-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -589,7 +512,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "production_01/west", "name": "production_01/west",
"nameNormalized": "production-01-west", "nameNormalized": "production-01-west",
"server": "https://production-01.example.com", "server": "https://production-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -608,7 +530,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "staging-01", "name": "staging-01",
"nameNormalized": "staging-01", "nameNormalized": "staging-01",
"server": "https://staging-01.example.com", "server": "https://staging-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -652,7 +573,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "staging-01", "name": "staging-01",
"nameNormalized": "staging-01", "nameNormalized": "staging-01",
"server": "https://staging-01.example.com", "server": "https://staging-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -677,163 +597,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
values: nil, values: nil,
expected: nil, expected: nil,
clientError: true, clientError: true,
expectedError: fmt.Errorf("error getting cluster secrets: could not list Secrets"), expectedError: fmt.Errorf("could not list Secrets"),
},
{
name: "Clusters with flat list mode and no selector",
selector: metav1.LabelSelector{},
isFlatMode: true,
values: map[string]string{
"lol1": "lol",
"lol2": "{{ .values.lol1 }}{{ .values.lol1 }}",
"lol3": "{{ .values.lol2 }}{{ .values.lol2 }}{{ .values.lol2 }}",
"foo": "bar",
"bar": "{{ if not (empty .metadata) }}{{index .metadata.annotations \"foo.argoproj.io\" }}{{ end }}",
"bat": "{{ if not (empty .metadata) }}{{.metadata.labels.environment}}{{ end }}",
"aaa": "{{ .server }}",
"no-op": "{{ .thisDoesNotExist }}",
},
expected: []map[string]interface{}{
{
"clusters": []map[string]interface{}{
{
"nameNormalized": "in-cluster",
"name": "in-cluster",
"server": "https://kubernetes.default.svc",
"project": "",
"values": map[string]string{
"lol1": "lol",
"lol2": "<no value><no value>",
"lol3": "<no value><no value><no value>",
"foo": "bar",
"bar": "",
"bat": "",
"aaa": "https://kubernetes.default.svc",
"no-op": "<no value>",
},
},
{
"name": "production_01/west",
"nameNormalized": "production-01-west",
"server": "https://production-01.example.com",
"project": "",
"metadata": map[string]interface{}{
"labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "production",
"org": "bar",
},
"annotations": map[string]string{
"foo.argoproj.io": "production",
},
},
"values": map[string]string{
"lol1": "lol",
"lol2": "<no value><no value>",
"lol3": "<no value><no value><no value>",
"foo": "bar",
"bar": "production",
"bat": "production",
"aaa": "https://production-01.example.com",
"no-op": "<no value>",
},
},
{
"name": "staging-01",
"nameNormalized": "staging-01",
"server": "https://staging-01.example.com",
"project": "",
"metadata": map[string]interface{}{
"labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "staging",
"org": "foo",
},
"annotations": map[string]string{
"foo.argoproj.io": "staging",
},
},
"values": map[string]string{
"lol1": "lol",
"lol2": "<no value><no value>",
"lol3": "<no value><no value><no value>",
"foo": "bar",
"bar": "staging",
"bat": "staging",
"aaa": "https://staging-01.example.com",
"no-op": "<no value>",
},
},
},
},
},
clientError: false,
expectedError: nil,
},
{
name: "production or staging with flat mode",
selector: metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "environment",
Operator: "In",
Values: []string{
"production",
"staging",
},
},
},
},
isFlatMode: true,
values: map[string]string{
"foo": "bar",
},
expected: []map[string]interface{}{
{
"clusters": []map[string]interface{}{
{
"name": "production_01/west",
"nameNormalized": "production-01-west",
"server": "https://production-01.example.com",
"project": "",
"metadata": map[string]interface{}{
"labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "production",
"org": "bar",
},
"annotations": map[string]string{
"foo.argoproj.io": "production",
},
},
"values": map[string]string{
"foo": "bar",
},
},
{
"name": "staging-01",
"nameNormalized": "staging-01",
"server": "https://staging-01.example.com",
"project": "",
"metadata": map[string]interface{}{
"labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "staging",
"org": "foo",
},
"annotations": map[string]string{
"foo.argoproj.io": "staging",
},
},
"values": map[string]string{
"foo": "bar",
},
},
},
},
},
clientError: false,
expectedError: nil,
}, },
} }
@@ -868,7 +632,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
Clusters: &argoprojiov1alpha1.ClusterGenerator{ Clusters: &argoprojiov1alpha1.ClusterGenerator{
Selector: testCase.selector, Selector: testCase.selector,
Values: testCase.values, Values: testCase.values,
FlatList: testCase.isFlatMode,
}, },
}, &applicationSetInfo, nil) }, &applicationSetInfo, nil)

View File

@@ -52,7 +52,7 @@ func (g *DuckTypeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.
return time.Duration(*appSetGenerator.ClusterDecisionResource.RequeueAfterSeconds) * time.Second return time.Duration(*appSetGenerator.ClusterDecisionResource.RequeueAfterSeconds) * time.Second
} }
return getDefaultRequeueAfter() return DefaultRequeueAfterSeconds
} }
func (g *DuckTypeGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate { func (g *DuckTypeGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
@@ -125,7 +125,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
duckResources, err := g.dynClient.Resource(duckGVR).Namespace(g.namespace).List(g.ctx, listOptions) duckResources, err := g.dynClient.Resource(duckGVR).Namespace(g.namespace).List(g.ctx, listOptions)
if err != nil { if err != nil {
log.WithField("GVK", duckGVR).Warning("resources were not found") log.WithField("GVK", duckGVR).Warning("resources were not found")
return nil, fmt.Errorf("failed to get dynamic resources: %w", err) return nil, err
} }
if len(duckResources.Items) == 0 { if len(duckResources.Items) == 0 {
@@ -218,7 +218,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
res = append(res, params) res = append(res, params)
} }
} else { } else {
log.Warningf("clusterDecisionResource status.%s missing", statusListKey) log.Warningf("clusterDecisionResource status." + statusListKey + " missing")
return nil, nil return nil, nil
} }

View File

@@ -199,7 +199,6 @@ func TestTransForm(t *testing.T) {
"name": "production_01/west", "name": "production_01/west",
"nameNormalized": "production-01-west", "nameNormalized": "production-01-west",
"server": "https://production-01.example.com", "server": "https://production-01.example.com",
"project": "",
}}, }},
}, },
{ {
@@ -215,7 +214,6 @@ func TestTransForm(t *testing.T) {
"name": "some-really-long-server-url", "name": "some-really-long-server-url",
"nameNormalized": "some-really-long-server-url", "nameNormalized": "some-really-long-server-url",
"server": "https://some-really-long-url-that-will-exceed-63-characters.com", "server": "https://some-really-long-url-that-will-exceed-63-characters.com",
"project": "",
}}, }},
}, },
} }

View File

@@ -48,7 +48,7 @@ func (g *GitGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Appli
return time.Duration(*appSetGenerator.Git.RequeueAfterSeconds) * time.Second return time.Duration(*appSetGenerator.Git.RequeueAfterSeconds) * time.Second
} }
return getDefaultRequeueAfter() return DefaultRequeueAfterSeconds
} }
func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error) { func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error) {
@@ -78,7 +78,7 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
return nil, fmt.Errorf("error getting project %s: %w", project, err) return nil, fmt.Errorf("error getting project %s: %w", project, err)
} }
// we need to verify the signature on the Git revision if GPG is enabled // we need to verify the signature on the Git revision if GPG is enabled
verifyCommit = len(appProject.Spec.SignatureKeys) > 0 && gpg.IsGPGEnabled() verifyCommit = appProject.Spec.SignatureKeys != nil && len(appProject.Spec.SignatureKeys) > 0 && gpg.IsGPGEnabled()
} }
var err error var err error

View File

@@ -7,9 +7,10 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/env"
) )
//go:generate go run github.com/vektra/mockery/v2@v2.40.2 --name=Generator
// Generator defines the interface implemented by all ApplicationSet generators. // Generator defines the interface implemented by all ApplicationSet generators.
type Generator interface { type Generator interface {
// GenerateParams interprets the ApplicationSet and generates all relevant parameters for the application template. // GenerateParams interprets the ApplicationSet and generates all relevant parameters for the application template.
@@ -31,11 +32,7 @@ var (
NoRequeueAfter time.Duration NoRequeueAfter time.Duration
) )
// DefaultRequeueAfterSeconds is used when GetRequeueAfter is not specified, it is the default time to wait before the next reconcile loop
const ( const (
DefaultRequeueAfterSeconds = 3 * time.Minute DefaultRequeueAfterSeconds = 3 * time.Minute
) )
func getDefaultRequeueAfter() time.Duration {
// Default is 3 minutes, min is 1 second, max is 1 year
return env.ParseDurationFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_REQUEUE_AFTER", DefaultRequeueAfterSeconds, 1*time.Second, 8760*time.Hour)
}

View File

@@ -1,29 +0,0 @@
package generators
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func Test_getDefaultRequeueAfter(t *testing.T) {
tests := []struct {
name string
requeueAfterEnv string
want time.Duration
}{
{name: "Default", requeueAfterEnv: "", want: DefaultRequeueAfterSeconds},
{name: "Min", requeueAfterEnv: "1s", want: 1 * time.Second},
{name: "Max", requeueAfterEnv: "8760h", want: 8760 * time.Hour},
{name: "Override", requeueAfterEnv: "10m", want: 10 * time.Minute},
{name: "LessThanMin", requeueAfterEnv: "1ms", want: DefaultRequeueAfterSeconds},
{name: "MoreThanMax", requeueAfterEnv: "8761h", want: DefaultRequeueAfterSeconds},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("ARGOCD_APPLICATIONSET_CONTROLLER_REQUEUE_AFTER", tt.requeueAfterEnv)
assert.Equalf(t, tt.want, getDefaultRequeueAfter(), "getDefaultRequeueAfter()")
})
}
}

View File

@@ -578,8 +578,8 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
}, },
}, },
expected: []map[string]interface{}{ expected: []map[string]interface{}{
{"path": "examples/git-generator-files-discovery/cluster-config/dev/config.json", "path.basename": "dev", "path.basenameNormalized": "dev", "name": "dev-01", "nameNormalized": "dev-01", "server": "https://dev-01.example.com", "metadata.labels.environment": "dev", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "project": ""}, {"path": "examples/git-generator-files-discovery/cluster-config/dev/config.json", "path.basename": "dev", "path.basenameNormalized": "dev", "name": "dev-01", "nameNormalized": "dev-01", "server": "https://dev-01.example.com", "metadata.labels.environment": "dev", "metadata.labels.argocd.argoproj.io/secret-type": "cluster"},
{"path": "examples/git-generator-files-discovery/cluster-config/prod/config.json", "path.basename": "prod", "path.basenameNormalized": "prod", "name": "prod-01", "nameNormalized": "prod-01", "server": "https://prod-01.example.com", "metadata.labels.environment": "prod", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "project": ""}, {"path": "examples/git-generator-files-discovery/cluster-config/prod/config.json", "path.basename": "prod", "path.basenameNormalized": "prod", "name": "prod-01", "nameNormalized": "prod-01", "server": "https://prod-01.example.com", "metadata.labels.environment": "prod", "metadata.labels.argocd.argoproj.io/secret-type": "cluster"},
}, },
clientError: false, clientError: false,
}, },
@@ -734,7 +734,6 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
"name": "dev-01", "name": "dev-01",
"nameNormalized": "dev-01", "nameNormalized": "dev-01",
"server": "https://dev-01.example.com", "server": "https://dev-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"environment": "dev", "environment": "dev",
@@ -751,7 +750,6 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
"name": "prod-01", "name": "prod-01",
"nameNormalized": "prod-01", "nameNormalized": "prod-01",
"server": "https://prod-01.example.com", "server": "https://prod-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"environment": "prod", "environment": "prod",
@@ -1075,7 +1073,7 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
// of that bug. // of that bug.
listGeneratorMock := &generatorMock{} listGeneratorMock := &generatorMock{}
listGeneratorMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).Return([]map[string]interface{}{ listGeneratorMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), mock.AnythingOfType("*v1alpha1.ApplicationSet")).Return([]map[string]interface{}{
{"some": "value"}, {"some": "value"},
}, nil) }, nil)
listGeneratorMock.On("GetTemplate", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator")).Return(&argoprojiov1alpha1.ApplicationSetTemplate{}) listGeneratorMock.On("GetTemplate", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator")).Return(&argoprojiov1alpha1.ApplicationSetTemplate{})

View File

@@ -197,7 +197,6 @@ func TestMergeGenerate(t *testing.T) {
} }
func toAPIExtensionsJSON(t *testing.T, g interface{}) *apiextensionsv1.JSON { func toAPIExtensionsJSON(t *testing.T, g interface{}) *apiextensionsv1.JSON {
t.Helper()
resVal, err := json.Marshal(g) resVal, err := json.Marshal(g)
if err != nil { if err != nil {
t.Error("unable to unmarshal json", g) t.Error("unable to unmarshal json", g)

View File

@@ -1,4 +1,4 @@
// Code generated by mockery v2.43.2. DO NOT EDIT. // Code generated by mockery v2.40.2. DO NOT EDIT.
package mocks package mocks

View File

@@ -694,7 +694,7 @@ func TestPluginGenerateParams(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
gotJson, err := json.Marshal(got) gotJson, err := json.Marshal(got)
require.NoError(t, err) require.NoError(t, err)
assert.JSONEq(t, string(expectedJson), string(gotJson)) assert.Equal(t, string(expectedJson), string(gotJson))
} }
}) })
} }

View File

@@ -6,12 +6,12 @@ import (
"strconv" "strconv"
"time" "time"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/gosimple/slug" "github.com/gosimple/slug"
pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request" pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
@@ -24,13 +24,19 @@ const (
type PullRequestGenerator struct { type PullRequestGenerator struct {
client client.Client client client.Client
selectServiceProviderFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) selectServiceProviderFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error)
SCMConfig auth SCMAuthProviders
scmRootCAPath string
allowedSCMProviders []string
enableSCMProviders bool
} }
func NewPullRequestGenerator(client client.Client, scmConfig SCMConfig) Generator { func NewPullRequestGenerator(client client.Client, auth SCMAuthProviders, scmRootCAPath string, allowedScmProviders []string, enableSCMProviders bool) Generator {
g := &PullRequestGenerator{ g := &PullRequestGenerator{
client: client, client: client,
SCMConfig: scmConfig, auth: auth,
scmRootCAPath: scmRootCAPath,
allowedSCMProviders: allowedScmProviders,
enableSCMProviders: enableSCMProviders,
} }
g.selectServiceProviderFunc = g.selectServiceProvider g.selectServiceProviderFunc = g.selectServiceProvider
return g return g
@@ -97,7 +103,6 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
paramMap := map[string]interface{}{ paramMap := map[string]interface{}{
"number": strconv.Itoa(pull.Number), "number": strconv.Itoa(pull.Number),
"title": pull.Title,
"branch": pull.Branch, "branch": pull.Branch,
"branch_slug": slug.Make(pull.Branch), "branch_slug": slug.Make(pull.Branch),
"target_branch": pull.TargetBranch, "target_branch": pull.TargetBranch,
@@ -105,7 +110,6 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
"head_sha": pull.HeadSHA, "head_sha": pull.HeadSHA,
"head_short_sha": pull.HeadSHA[:shortSHALength], "head_short_sha": pull.HeadSHA[:shortSHALength],
"head_short_sha_7": pull.HeadSHA[:shortSHALength7], "head_short_sha_7": pull.HeadSHA[:shortSHALength7],
"author": pull.Author,
} }
// PR lables will only be supported for Go Template appsets, since fasttemplate will be deprecated. // PR lables will only be supported for Go Template appsets, since fasttemplate will be deprecated.
@@ -131,23 +135,15 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
} }
if generatorConfig.GitLab != nil { if generatorConfig.GitLab != nil {
providerConfig := generatorConfig.GitLab providerConfig := generatorConfig.GitLab
var caCerts []byte token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace)
var prErr error
if providerConfig.CARef != nil {
caCerts, prErr = utils.GetConfigMapData(ctx, g.client, providerConfig.CARef, applicationSetInfo.Namespace)
if prErr != nil {
return nil, fmt.Errorf("error fetching CA certificates from ConfigMap: %w", prErr)
}
}
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %w", err)
} }
return pullrequest.NewGitLabService(ctx, token, providerConfig.API, providerConfig.Project, providerConfig.Labels, providerConfig.PullRequestState, g.scmRootCAPath, providerConfig.Insecure, caCerts) return pullrequest.NewGitLabService(ctx, token, providerConfig.API, providerConfig.Project, providerConfig.Labels, providerConfig.PullRequestState, g.scmRootCAPath, providerConfig.Insecure)
} }
if generatorConfig.Gitea != nil { if generatorConfig.Gitea != nil {
providerConfig := generatorConfig.Gitea providerConfig := generatorConfig.Gitea
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace)
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)
} }
@@ -155,40 +151,26 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
} }
if generatorConfig.BitbucketServer != nil { if generatorConfig.BitbucketServer != nil {
providerConfig := generatorConfig.BitbucketServer providerConfig := generatorConfig.BitbucketServer
var caCerts []byte if providerConfig.BasicAuth != nil {
var prErr error password, err := g.getSecretRef(ctx, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace)
if providerConfig.CARef != nil {
caCerts, prErr = utils.GetConfigMapData(ctx, g.client, providerConfig.CARef, applicationSetInfo.Namespace)
if prErr != nil {
return nil, fmt.Errorf("error fetching CA certificates from ConfigMap: %w", prErr)
}
}
if providerConfig.BearerToken != nil {
appToken, err := utils.GetSecretRef(ctx, g.client, providerConfig.BearerToken.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
if err != nil {
return nil, fmt.Errorf("error fetching Secret Bearer token: %w", err)
}
return pullrequest.NewBitbucketServiceBearerToken(ctx, appToken, providerConfig.API, providerConfig.Project, providerConfig.Repo, g.scmRootCAPath, providerConfig.Insecure, caCerts)
} else if providerConfig.BasicAuth != nil {
password, err := utils.GetSecretRef(ctx, g.client, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %w", err)
} }
return pullrequest.NewBitbucketServiceBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.Repo, g.scmRootCAPath, providerConfig.Insecure, caCerts) return pullrequest.NewBitbucketServiceBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.Repo)
} else { } else {
return pullrequest.NewBitbucketServiceNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.Repo, g.scmRootCAPath, providerConfig.Insecure, caCerts) return pullrequest.NewBitbucketServiceNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.Repo)
} }
} }
if generatorConfig.Bitbucket != nil { if generatorConfig.Bitbucket != nil {
providerConfig := generatorConfig.Bitbucket providerConfig := generatorConfig.Bitbucket
if providerConfig.BearerToken != nil { if providerConfig.BearerToken != nil {
appToken, err := utils.GetSecretRef(ctx, g.client, providerConfig.BearerToken.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) appToken, err := g.getSecretRef(ctx, providerConfig.BearerToken.TokenRef, applicationSetInfo.Namespace)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Secret Bearer token: %w", err) return nil, fmt.Errorf("error fetching Secret Bearer token: %w", err)
} }
return pullrequest.NewBitbucketCloudServiceBearerToken(providerConfig.API, appToken, providerConfig.Owner, providerConfig.Repo) return pullrequest.NewBitbucketCloudServiceBearerToken(providerConfig.API, appToken, providerConfig.Owner, providerConfig.Repo)
} else if providerConfig.BasicAuth != nil { } else if providerConfig.BasicAuth != nil {
password, err := utils.GetSecretRef(ctx, g.client, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) password, err := g.getSecretRef(ctx, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace)
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)
} }
@@ -199,7 +181,7 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
} }
if generatorConfig.AzureDevOps != nil { if generatorConfig.AzureDevOps != nil {
providerConfig := generatorConfig.AzureDevOps providerConfig := generatorConfig.AzureDevOps
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace)
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)
} }
@@ -211,7 +193,7 @@ 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) {
// 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.auth.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)
} }
@@ -219,9 +201,33 @@ func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alph
} }
// always default to token, even if not set (public access) // always default to token, even if not set (public access)
token, err := utils.GetSecretRef(ctx, g.client, cfg.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) token, err := g.getSecretRef(ctx, cfg.TokenRef, applicationSetInfo.Namespace)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %w", err)
} }
return pullrequest.NewGithubService(ctx, token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels) return pullrequest.NewGithubService(ctx, token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
} }
// getSecretRef gets the value of the key for the specified Secret resource.
func (g *PullRequestGenerator) getSecretRef(ctx context.Context, ref *argoprojiov1alpha1.SecretRef, namespace string) (string, error) {
if ref == nil {
return "", nil
}
secret := &corev1.Secret{}
err := g.client.Get(
ctx,
client.ObjectKey{
Name: ref.SecretName,
Namespace: namespace,
},
secret)
if err != nil {
return "", fmt.Errorf("error fetching secret %s/%s: %w", namespace, ref.SecretName, err)
}
tokenBytes, ok := secret.Data[ref.Key]
if !ok {
return "", fmt.Errorf("key %q in secret %s/%s not found", ref.Key, namespace, ref.SecretName)
}
return string(tokenBytes), nil
}

View File

@@ -7,7 +7,9 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request" pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
@@ -28,11 +30,9 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
[]*pullrequest.PullRequest{ []*pullrequest.PullRequest{
{ {
Number: 1, Number: 1,
Title: "title1",
Branch: "branch1", Branch: "branch1",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "testName",
}, },
}, },
nil, nil,
@@ -41,7 +41,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
expected: []map[string]interface{}{ expected: []map[string]interface{}{
{ {
"number": "1", "number": "1",
"title": "title1",
"branch": "branch1", "branch": "branch1",
"branch_slug": "branch1", "branch_slug": "branch1",
"target_branch": "master", "target_branch": "master",
@@ -49,7 +48,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958", "head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb", "head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c", "head_short_sha_7": "089d92c",
"author": "testName",
}, },
}, },
expectedErr: nil, expectedErr: nil,
@@ -61,11 +59,9 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
[]*pullrequest.PullRequest{ []*pullrequest.PullRequest{
{ {
Number: 2, Number: 2,
Title: "title2",
Branch: "feat/areally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature", Branch: "feat/areally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
TargetBranch: "feat/anotherreally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature", TargetBranch: "feat/anotherreally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
HeadSHA: "9b34ff5bd418e57d58891eb0aa0728043ca1e8be", HeadSHA: "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
Author: "testName",
}, },
}, },
nil, nil,
@@ -74,7 +70,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
expected: []map[string]interface{}{ expected: []map[string]interface{}{
{ {
"number": "2", "number": "2",
"title": "title2",
"branch": "feat/areally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature", "branch": "feat/areally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
"branch_slug": "feat-areally-long-pull-request-name-to-test-argo", "branch_slug": "feat-areally-long-pull-request-name-to-test-argo",
"target_branch": "feat/anotherreally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature", "target_branch": "feat/anotherreally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
@@ -82,7 +77,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"head_sha": "9b34ff5bd418e57d58891eb0aa0728043ca1e8be", "head_sha": "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
"head_short_sha": "9b34ff5b", "head_short_sha": "9b34ff5b",
"head_short_sha_7": "9b34ff5", "head_short_sha_7": "9b34ff5",
"author": "testName",
}, },
}, },
expectedErr: nil, expectedErr: nil,
@@ -94,11 +88,9 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
[]*pullrequest.PullRequest{ []*pullrequest.PullRequest{
{ {
Number: 1, Number: 1,
Title: "title1",
Branch: "a-very-short-sha", Branch: "a-very-short-sha",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "abcd", HeadSHA: "abcd",
Author: "testName",
}, },
}, },
nil, nil,
@@ -107,7 +99,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
expected: []map[string]interface{}{ expected: []map[string]interface{}{
{ {
"number": "1", "number": "1",
"title": "title1",
"branch": "a-very-short-sha", "branch": "a-very-short-sha",
"branch_slug": "a-very-short-sha", "branch_slug": "a-very-short-sha",
"target_branch": "master", "target_branch": "master",
@@ -115,7 +106,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"head_sha": "abcd", "head_sha": "abcd",
"head_short_sha": "abcd", "head_short_sha": "abcd",
"head_short_sha_7": "abcd", "head_short_sha_7": "abcd",
"author": "testName",
}, },
}, },
expectedErr: nil, expectedErr: nil,
@@ -138,12 +128,10 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
[]*pullrequest.PullRequest{ []*pullrequest.PullRequest{
{ {
Number: 1, Number: 1,
Title: "title1",
Branch: "branch1", Branch: "branch1",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Labels: []string{"preview"}, Labels: []string{"preview"},
Author: "testName",
}, },
}, },
nil, nil,
@@ -152,7 +140,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
expected: []map[string]interface{}{ expected: []map[string]interface{}{
{ {
"number": "1", "number": "1",
"title": "title1",
"branch": "branch1", "branch": "branch1",
"branch_slug": "branch1", "branch_slug": "branch1",
"target_branch": "master", "target_branch": "master",
@@ -161,7 +148,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"head_short_sha": "089d92cb", "head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c", "head_short_sha_7": "089d92c",
"labels": []string{"preview"}, "labels": []string{"preview"},
"author": "testName",
}, },
}, },
expectedErr: nil, expectedErr: nil,
@@ -179,12 +165,10 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
[]*pullrequest.PullRequest{ []*pullrequest.PullRequest{
{ {
Number: 1, Number: 1,
Title: "title1",
Branch: "branch1", Branch: "branch1",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Labels: []string{"preview"}, Labels: []string{"preview"},
Author: "testName",
}, },
}, },
nil, nil,
@@ -193,7 +177,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
expected: []map[string]interface{}{ expected: []map[string]interface{}{
{ {
"number": "1", "number": "1",
"title": "title1",
"branch": "branch1", "branch": "branch1",
"branch_slug": "branch1", "branch_slug": "branch1",
"target_branch": "master", "target_branch": "master",
@@ -201,7 +184,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958", "head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb", "head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c", "head_short_sha_7": "089d92c",
"author": "testName",
}, },
}, },
expectedErr: nil, expectedErr: nil,
@@ -232,10 +214,76 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
} }
} }
func TestPullRequestGetSecretRef(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test-secret", Namespace: "test"},
Data: map[string][]byte{
"my-token": []byte("secret"),
},
}
gen := &PullRequestGenerator{client: fake.NewClientBuilder().WithObjects(secret).Build()}
ctx := context.Background()
cases := []struct {
name, namespace, token string
ref *argoprojiov1alpha1.SecretRef
hasError bool
}{
{
name: "valid ref",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
namespace: "test",
token: "secret",
hasError: false,
},
{
name: "nil ref",
ref: nil,
namespace: "test",
token: "",
hasError: false,
},
{
name: "wrong name",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "other", Key: "my-token"},
namespace: "test",
token: "",
hasError: true,
},
{
name: "wrong key",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "other-token"},
namespace: "test",
token: "",
hasError: true,
},
{
name: "wrong namespace",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
namespace: "other",
token: "",
hasError: true,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
token, err := gen.getSecretRef(ctx, c.ref, c.namespace)
if c.hasError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
assert.Equal(t, c.token, token)
})
}
}
func TestAllowedSCMProviderPullRequest(t *testing.T) { func TestAllowedSCMProviderPullRequest(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
providerConfig *argoprojiov1alpha1.PullRequestGenerator providerConfig *argoprojiov1alpha1.PullRequestGenerator
expectedError error
}{ }{
{ {
name: "Error Github", name: "Error Github",
@@ -244,6 +292,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local", API: "https://myservice.mynamespace.svc.cluster.local",
}, },
}, },
expectedError: &ErrDisallowedSCMProvider{},
}, },
{ {
name: "Error Gitlab", name: "Error Gitlab",
@@ -252,6 +301,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local", API: "https://myservice.mynamespace.svc.cluster.local",
}, },
}, },
expectedError: &ErrDisallowedSCMProvider{},
}, },
{ {
name: "Error Gitea", name: "Error Gitea",
@@ -260,6 +310,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local", API: "https://myservice.mynamespace.svc.cluster.local",
}, },
}, },
expectedError: &ErrDisallowedSCMProvider{},
}, },
{ {
name: "Error Bitbucket", name: "Error Bitbucket",
@@ -268,6 +319,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local", API: "https://myservice.mynamespace.svc.cluster.local",
}, },
}, },
expectedError: &ErrDisallowedSCMProvider{},
}, },
} }
@@ -277,13 +329,13 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) { t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel() t.Parallel()
pullRequestGenerator := NewPullRequestGenerator(nil, NewSCMConfig("", []string{ pullRequestGenerator := NewPullRequestGenerator(nil, SCMAuthProviders{}, "", []string{
"github.myorg.com", "github.myorg.com",
"gitlab.myorg.com", "gitlab.myorg.com",
"gitea.myorg.com", "gitea.myorg.com",
"bitbucket.myorg.com", "bitbucket.myorg.com",
"azuredevops.myorg.com", "azuredevops.myorg.com",
}, true, nil, true)) }, true)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@@ -299,14 +351,13 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
_, err := pullRequestGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil) _, err := pullRequestGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
require.Error(t, err, "Must return an error") require.Error(t, err, "Must return an error")
var expectedError ErrDisallowedSCMProvider assert.ErrorAs(t, err, testCaseCopy.expectedError)
assert.ErrorAs(t, err, &expectedError)
}) })
} }
} }
func TestSCMProviderDisabled_PRGenerator(t *testing.T) { func TestSCMProviderDisabled_PRGenerator(t *testing.T) {
generator := NewPullRequestGenerator(nil, NewSCMConfig("", []string{}, false, nil, true)) generator := NewPullRequestGenerator(nil, SCMAuthProviders{}, "", []string{}, false)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{

View File

@@ -7,6 +7,7 @@ import (
"strings" "strings"
"time" "time"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@@ -28,38 +29,29 @@ type SCMProviderGenerator struct {
client client.Client client client.Client
// Testing hooks. // Testing hooks.
overrideProvider scm_provider.SCMProviderService overrideProvider scm_provider.SCMProviderService
SCMConfig SCMAuthProviders
}
type SCMConfig struct {
scmRootCAPath string scmRootCAPath string
allowedSCMProviders []string allowedSCMProviders []string
enableSCMProviders bool enableSCMProviders bool
GitHubApps github_app_auth.Credentials
tokenRefStrictMode bool
} }
func NewSCMConfig(scmRootCAPath string, allowedSCMProviders []string, enableSCMProviders bool, gitHubApps github_app_auth.Credentials, tokenRefStrictMode bool) SCMConfig { type SCMAuthProviders struct {
return SCMConfig{ GitHubApps github_app_auth.Credentials
}
func NewSCMProviderGenerator(client client.Client, providers SCMAuthProviders, scmRootCAPath string, allowedSCMProviders []string, enableSCMProviders bool) Generator {
return &SCMProviderGenerator{
client: client,
SCMAuthProviders: providers,
scmRootCAPath: scmRootCAPath, scmRootCAPath: scmRootCAPath,
allowedSCMProviders: allowedSCMProviders, allowedSCMProviders: allowedSCMProviders,
enableSCMProviders: enableSCMProviders, enableSCMProviders: enableSCMProviders,
GitHubApps: gitHubApps,
tokenRefStrictMode: tokenRefStrictMode,
}
}
func NewSCMProviderGenerator(client client.Client, scmConfig SCMConfig) Generator {
return &SCMProviderGenerator{
client: client,
SCMConfig: scmConfig,
} }
} }
// Testing generator // Testing generator
func NewTestSCMProviderGenerator(overrideProvider scm_provider.SCMProviderService) Generator { func NewTestSCMProviderGenerator(overrideProvider scm_provider.SCMProviderService) Generator {
return &SCMProviderGenerator{overrideProvider: overrideProvider, SCMConfig: SCMConfig{ return &SCMProviderGenerator{overrideProvider: overrideProvider, enableSCMProviders: true}
enableSCMProviders: true,
}}
} }
func (g *SCMProviderGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration { func (g *SCMProviderGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
@@ -147,25 +139,16 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
return nil, fmt.Errorf("scm provider: %w", err) return nil, fmt.Errorf("scm provider: %w", err)
} }
} else if providerConfig.Gitlab != nil { } else if providerConfig.Gitlab != nil {
providerConfig := providerConfig.Gitlab token, err := g.getSecretRef(ctx, providerConfig.Gitlab.TokenRef, applicationSetInfo.Namespace)
var caCerts []byte
var scmError error
if providerConfig.CARef != nil {
caCerts, scmError = utils.GetConfigMapData(ctx, g.client, providerConfig.CARef, applicationSetInfo.Namespace)
if scmError != nil {
return nil, fmt.Errorf("error fetching CA certificates from ConfigMap: %w", scmError)
}
}
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Gitlab token: %w", err) return nil, fmt.Errorf("error fetching Gitlab token: %w", err)
} }
provider, err = scm_provider.NewGitlabProvider(ctx, providerConfig.Group, token, providerConfig.API, providerConfig.AllBranches, providerConfig.IncludeSubgroups, providerConfig.WillIncludeSharedProjects(), providerConfig.Insecure, g.scmRootCAPath, providerConfig.Topic, caCerts) provider, err = scm_provider.NewGitlabProvider(ctx, providerConfig.Gitlab.Group, token, providerConfig.Gitlab.API, providerConfig.Gitlab.AllBranches, providerConfig.Gitlab.IncludeSubgroups, providerConfig.Gitlab.WillIncludeSharedProjects(), providerConfig.Gitlab.Insecure, g.scmRootCAPath, providerConfig.Gitlab.Topic)
if err != nil { if err != nil {
return nil, fmt.Errorf("error initializing Gitlab service: %w", err) return nil, fmt.Errorf("error initializing Gitlab service: %w", err)
} }
} else if providerConfig.Gitea != nil { } else if providerConfig.Gitea != nil {
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.Gitea.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) token, err := g.getSecretRef(ctx, providerConfig.Gitea.TokenRef, applicationSetInfo.Namespace)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Gitea token: %w", err) return nil, fmt.Errorf("error fetching Gitea token: %w", err)
} }
@@ -175,34 +158,21 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
} }
} else if providerConfig.BitbucketServer != nil { } else if providerConfig.BitbucketServer != nil {
providerConfig := providerConfig.BitbucketServer providerConfig := providerConfig.BitbucketServer
var caCerts []byte
var scmError error var scmError error
if providerConfig.CARef != nil { if providerConfig.BasicAuth != nil {
caCerts, scmError = utils.GetConfigMapData(ctx, g.client, providerConfig.CARef, applicationSetInfo.Namespace) password, err := g.getSecretRef(ctx, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace)
if scmError != nil {
return nil, fmt.Errorf("error fetching CA certificates from ConfigMap: %w", scmError)
}
}
if providerConfig.BearerToken != nil {
appToken, err := utils.GetSecretRef(ctx, g.client, providerConfig.BearerToken.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
if err != nil {
return nil, fmt.Errorf("error fetching Secret Bearer token: %w", err)
}
provider, scmError = scm_provider.NewBitbucketServerProviderBearerToken(ctx, appToken, providerConfig.API, providerConfig.Project, providerConfig.AllBranches, g.scmRootCAPath, providerConfig.Insecure, caCerts)
} else if providerConfig.BasicAuth != nil {
password, err := utils.GetSecretRef(ctx, g.client, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %w", err)
} }
provider, scmError = scm_provider.NewBitbucketServerProviderBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.AllBranches, g.scmRootCAPath, providerConfig.Insecure, caCerts) provider, scmError = scm_provider.NewBitbucketServerProviderBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.AllBranches)
} else { } else {
provider, scmError = scm_provider.NewBitbucketServerProviderNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.AllBranches, g.scmRootCAPath, providerConfig.Insecure, caCerts) provider, scmError = scm_provider.NewBitbucketServerProviderNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.AllBranches)
} }
if scmError != nil { if scmError != nil {
return nil, fmt.Errorf("error initializing Bitbucket Server service: %w", scmError) return nil, fmt.Errorf("error initializing Bitbucket Server service: %w", scmError)
} }
} else if providerConfig.AzureDevOps != nil { } else if providerConfig.AzureDevOps != nil {
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.AzureDevOps.AccessTokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) token, err := g.getSecretRef(ctx, providerConfig.AzureDevOps.AccessTokenRef, applicationSetInfo.Namespace)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Azure Devops access token: %w", err) return nil, fmt.Errorf("error fetching Azure Devops access token: %w", err)
} }
@@ -211,7 +181,7 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
return nil, fmt.Errorf("error initializing Azure Devops service: %w", err) return nil, fmt.Errorf("error initializing Azure Devops service: %w", err)
} }
} else if providerConfig.Bitbucket != nil { } else if providerConfig.Bitbucket != nil {
appPassword, err := utils.GetSecretRef(ctx, g.client, providerConfig.Bitbucket.AppPasswordRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) appPassword, err := g.getSecretRef(ctx, providerConfig.Bitbucket.AppPasswordRef, applicationSetInfo.Namespace)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Bitbucket cloud appPassword: %w", err) return nil, fmt.Errorf("error fetching Bitbucket cloud appPassword: %w", err)
} }
@@ -270,6 +240,29 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
return paramsArray, nil return paramsArray, nil
} }
func (g *SCMProviderGenerator) getSecretRef(ctx context.Context, ref *argoprojiov1alpha1.SecretRef, namespace string) (string, error) {
if ref == nil {
return "", nil
}
secret := &corev1.Secret{}
err := g.client.Get(
ctx,
client.ObjectKey{
Name: ref.SecretName,
Namespace: namespace,
},
secret)
if err != nil {
return "", fmt.Errorf("error fetching secret %s/%s: %w", namespace, ref.SecretName, err)
}
tokenBytes, ok := secret.Data[ref.Key]
if !ok {
return "", fmt.Errorf("key %q in secret %s/%s not found", ref.Key, namespace, ref.SecretName)
}
return string(tokenBytes), nil
}
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) {
if github.AppSecretName != "" { if github.AppSecretName != "" {
auth, err := g.GitHubApps.GetAuthSecret(ctx, github.AppSecretName) auth, err := g.GitHubApps.GetAuthSecret(ctx, github.AppSecretName)
@@ -285,7 +278,7 @@ func (g *SCMProviderGenerator) githubProvider(ctx context.Context, github *argop
) )
} }
token, err := utils.GetSecretRef(ctx, g.client, github.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) token, err := g.getSecretRef(ctx, github.TokenRef, applicationSetInfo.Namespace)
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)
} }

View File

@@ -1,16 +1,84 @@
package generators package generators
import ( import (
"context"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider" "github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func TestSCMProviderGetSecretRef(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test-secret", Namespace: "test"},
Data: map[string][]byte{
"my-token": []byte("secret"),
},
}
gen := &SCMProviderGenerator{client: fake.NewClientBuilder().WithObjects(secret).Build()}
ctx := context.Background()
cases := []struct {
name, namespace, token string
ref *argoprojiov1alpha1.SecretRef
hasError bool
}{
{
name: "valid ref",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
namespace: "test",
token: "secret",
hasError: false,
},
{
name: "nil ref",
ref: nil,
namespace: "test",
token: "",
hasError: false,
},
{
name: "wrong name",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "other", Key: "my-token"},
namespace: "test",
token: "",
hasError: true,
},
{
name: "wrong key",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "other-token"},
namespace: "test",
token: "",
hasError: true,
},
{
name: "wrong namespace",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
namespace: "other",
token: "",
hasError: true,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
token, err := gen.getSecretRef(ctx, c.ref, c.namespace)
if c.hasError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
assert.Equal(t, c.token, token)
})
}
}
func TestSCMProviderGenerateParams(t *testing.T) { func TestSCMProviderGenerateParams(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
@@ -106,7 +174,7 @@ func TestSCMProviderGenerateParams(t *testing.T) {
mockProvider := &scm_provider.MockProvider{ mockProvider := &scm_provider.MockProvider{
Repos: testCaseCopy.repos, Repos: testCaseCopy.repos,
} }
scmGenerator := &SCMProviderGenerator{overrideProvider: mockProvider, SCMConfig: SCMConfig{enableSCMProviders: true}} scmGenerator := &SCMProviderGenerator{overrideProvider: mockProvider, enableSCMProviders: true}
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "set", Name: "set",
@@ -136,6 +204,7 @@ func TestAllowedSCMProvider(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
providerConfig *argoprojiov1alpha1.SCMProviderGenerator providerConfig *argoprojiov1alpha1.SCMProviderGenerator
expectedError error
}{ }{
{ {
name: "Error Github", name: "Error Github",
@@ -144,6 +213,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local", API: "https://myservice.mynamespace.svc.cluster.local",
}, },
}, },
expectedError: &ErrDisallowedSCMProvider{},
}, },
{ {
name: "Error Gitlab", name: "Error Gitlab",
@@ -152,6 +222,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local", API: "https://myservice.mynamespace.svc.cluster.local",
}, },
}, },
expectedError: &ErrDisallowedSCMProvider{},
}, },
{ {
name: "Error Gitea", name: "Error Gitea",
@@ -160,6 +231,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local", API: "https://myservice.mynamespace.svc.cluster.local",
}, },
}, },
expectedError: &ErrDisallowedSCMProvider{},
}, },
{ {
name: "Error Bitbucket", name: "Error Bitbucket",
@@ -168,6 +240,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local", API: "https://myservice.mynamespace.svc.cluster.local",
}, },
}, },
expectedError: &ErrDisallowedSCMProvider{},
}, },
{ {
name: "Error AzureDevops", name: "Error AzureDevops",
@@ -176,6 +249,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local", API: "https://myservice.mynamespace.svc.cluster.local",
}, },
}, },
expectedError: &ErrDisallowedSCMProvider{},
}, },
} }
@@ -186,16 +260,14 @@ func TestAllowedSCMProvider(t *testing.T) {
t.Parallel() t.Parallel()
scmGenerator := &SCMProviderGenerator{ scmGenerator := &SCMProviderGenerator{
SCMConfig: SCMConfig{ allowedSCMProviders: []string{
allowedSCMProviders: []string{ "github.myorg.com",
"github.myorg.com", "gitlab.myorg.com",
"gitlab.myorg.com", "gitea.myorg.com",
"gitea.myorg.com", "bitbucket.myorg.com",
"bitbucket.myorg.com", "azuredevops.myorg.com",
"azuredevops.myorg.com",
},
enableSCMProviders: true,
}, },
enableSCMProviders: true,
} }
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
@@ -212,14 +284,13 @@ func TestAllowedSCMProvider(t *testing.T) {
_, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil) _, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
require.Error(t, err, "Must return an error") require.Error(t, err, "Must return an error")
var expectedError ErrDisallowedSCMProvider assert.ErrorAs(t, err, testCaseCopy.expectedError)
assert.ErrorAs(t, err, &expectedError)
}) })
} }
} }
func TestSCMProviderDisabled_SCMGenerator(t *testing.T) { func TestSCMProviderDisabled_SCMGenerator(t *testing.T) {
generator := &SCMProviderGenerator{SCMConfig: SCMConfig{enableSCMProviders: false}} generator := &SCMProviderGenerator{enableSCMProviders: false}
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{

View File

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

View File

@@ -1,22 +0,0 @@
package metrics
import (
"github.com/prometheus/client_golang/prometheus"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
)
// Fake implementation for testing
func NewFakeAppsetMetrics(client ctrlclient.WithWatch) *ApplicationsetMetrics {
reconcileHistogram := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "argocd_appset_reconcile",
Help: "Application reconciliation performance in seconds.",
// Buckets can be set later on after observing median time
},
[]string{"name", "namespace"},
)
return &ApplicationsetMetrics{
reconcileHistogram: reconcileHistogram,
}
}

View File

@@ -1,131 +0,0 @@
package metrics
import (
"time"
"github.com/prometheus/client_golang/prometheus"
"k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/metrics"
argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
applisters "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
metricsutil "github.com/argoproj/argo-cd/v2/util/metrics"
)
var (
descAppsetLabels *prometheus.Desc
descAppsetDefaultLabels = []string{"namespace", "name"}
descAppsetInfo = prometheus.NewDesc(
"argocd_appset_info",
"Information about applicationset",
append(descAppsetDefaultLabels, "resource_update_status"),
nil,
)
descAppsetGeneratedApps = prometheus.NewDesc(
"argocd_appset_owned_applications",
"Number of applications owned by the applicationset",
descAppsetDefaultLabels,
nil,
)
)
type ApplicationsetMetrics struct {
reconcileHistogram *prometheus.HistogramVec
}
type appsetCollector struct {
lister applisters.ApplicationSetLister
// appsClientSet appclientset.Interface
labels []string
filter func(appset *argoappv1.ApplicationSet) bool
}
func NewApplicationsetMetrics(appsetLister applisters.ApplicationSetLister, appsetLabels []string, appsetFilter func(appset *argoappv1.ApplicationSet) bool) ApplicationsetMetrics {
reconcileHistogram := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "argocd_appset_reconcile",
Help: "Application reconciliation performance in seconds.",
// Buckets can be set later on after observing median time
},
descAppsetDefaultLabels,
)
appsetCollector := newAppsetCollector(appsetLister, appsetLabels, appsetFilter)
// Register collectors and metrics
metrics.Registry.MustRegister(reconcileHistogram)
metrics.Registry.MustRegister(appsetCollector)
return ApplicationsetMetrics{
reconcileHistogram: reconcileHistogram,
}
}
func (m *ApplicationsetMetrics) ObserveReconcile(appset *argoappv1.ApplicationSet, duration time.Duration) {
m.reconcileHistogram.WithLabelValues(appset.Namespace, appset.Name).Observe(duration.Seconds())
}
func newAppsetCollector(lister applisters.ApplicationSetLister, labels []string, filter func(appset *argoappv1.ApplicationSet) bool) *appsetCollector {
descAppsetDefaultLabels = []string{"namespace", "name"}
if len(labels) > 0 {
descAppsetLabels = prometheus.NewDesc(
"argocd_appset_labels",
"Applicationset labels translated to Prometheus labels",
append(descAppsetDefaultLabels, metricsutil.NormalizeLabels("label", labels)...),
nil,
)
}
return &appsetCollector{
lister: lister,
labels: labels,
filter: filter,
}
}
// Describe implements the prometheus.Collector interface
func (c *appsetCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- descAppsetInfo
ch <- descAppsetGeneratedApps
if len(c.labels) > 0 {
ch <- descAppsetLabels
}
}
// Collect implements the prometheus.Collector interface
func (c *appsetCollector) Collect(ch chan<- prometheus.Metric) {
appsets, _ := c.lister.List(labels.NewSelector())
for _, appset := range appsets {
if c.filter(appset) {
collectAppset(appset, c.labels, ch)
}
}
}
func collectAppset(appset *argoappv1.ApplicationSet, labelsToCollect []string, ch chan<- prometheus.Metric) {
labelValues := make([]string, 0)
commonLabelValues := []string{appset.Namespace, appset.Name}
for _, label := range labelsToCollect {
labelValues = append(labelValues, appset.GetLabels()[label])
}
resourceUpdateStatus := "Unknown"
for _, condition := range appset.Status.Conditions {
if condition.Type == argoappv1.ApplicationSetConditionResourcesUpToDate {
resourceUpdateStatus = condition.Reason
}
}
if len(labelsToCollect) > 0 {
ch <- prometheus.MustNewConstMetric(descAppsetLabels, prometheus.GaugeValue, 1, append(commonLabelValues, labelValues...)...)
}
ch <- prometheus.MustNewConstMetric(descAppsetInfo, prometheus.GaugeValue, 1, appset.Namespace, appset.Name, resourceUpdateStatus)
ch <- prometheus.MustNewConstMetric(descAppsetGeneratedApps, prometheus.GaugeValue, float64(len(appset.Status.Resources)), appset.Namespace, appset.Name)
}

View File

@@ -1,256 +0,0 @@
package metrics
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
fake "sigs.k8s.io/controller-runtime/pkg/client/fake"
prometheus "github.com/prometheus/client_golang/prometheus"
metricsutil "github.com/argoproj/argo-cd/v2/util/metrics"
"sigs.k8s.io/controller-runtime/pkg/metrics"
"sigs.k8s.io/yaml"
)
var (
applicationsetNamespaces = []string{"argocd", "test-namespace1"}
filter = func(appset *argoappv1.ApplicationSet) bool {
return utils.IsNamespaceAllowed(applicationsetNamespaces, appset.Namespace)
}
collectedLabels = []string{"included/test"}
)
const fakeAppsetList = `
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: test1
namespace: argocd
labels:
included/test: test
not-included.label/test: test
spec:
generators:
- git:
directories:
- path: test/*
repoURL: https://github.com/test/test.git
revision: HEAD
template:
metadata:
name: '{{.path.basename}}'
spec:
destination:
namespace: '{{.path.basename}}'
server: https://kubernetes.default.svc
project: default
source:
path: '{{.path.path}}'
repoURL: https://github.com/test/test.git
targetRevision: HEAD
status:
resources:
- group: argoproj.io
health:
status: Missing
kind: Application
name: test-app1
namespace: argocd
status: OutOfSync
version: v1alpha1
- group: argoproj.io
health:
status: Missing
kind: Application
name: test-app2
namespace: argocd
status: OutOfSync
version: v1alpha1
conditions:
- lastTransitionTime: "2024-01-01T00:00:00Z"
message: Successfully generated parameters for all Applications
reason: ApplicationSetUpToDate
status: "False"
type: ErrorOccurred
- lastTransitionTime: "2024-01-01T00:00:00Z"
message: Successfully generated parameters for all Applications
reason: ParametersGenerated
status: "True"
type: ParametersGenerated
- lastTransitionTime: "2024-01-01T00:00:00Z"
message: ApplicationSet up to date
reason: ApplicationSetUpToDate
status: "True"
type: ResourcesUpToDate
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: test2
namespace: argocd
labels:
not-included.label/test: test
spec:
generators:
- git:
directories:
- path: test/*
repoURL: https://github.com/test/test.git
revision: HEAD
template:
metadata:
name: '{{.path.basename}}'
spec:
destination:
namespace: '{{.path.basename}}'
server: https://kubernetes.default.svc
project: default
source:
path: '{{.path.path}}'
repoURL: https://github.com/test/test.git
targetRevision: HEAD
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: should-be-filtered-out
namespace: not-allowed
spec:
generators:
- git:
directories:
- path: test/*
repoURL: https://github.com/test/test.git
revision: HEAD
template:
metadata:
name: '{{.path.basename}}'
spec:
destination:
namespace: '{{.path.basename}}'
server: https://kubernetes.default.svc
project: default
source:
path: '{{.path.path}}'
repoURL: https://github.com/test/test.git
targetRevision: HEAD
`
func newFakeAppsets(fakeAppsetYAML string) []argoappv1.ApplicationSet {
var results []argoappv1.ApplicationSet
appsetRawYamls := strings.Split(fakeAppsetYAML, "---")
for _, appsetRawYaml := range appsetRawYamls {
var appset argoappv1.ApplicationSet
err := yaml.Unmarshal([]byte(appsetRawYaml), &appset)
if err != nil {
panic(err)
}
results = append(results, appset)
}
return results
}
func TestApplicationsetCollector(t *testing.T) {
appsetList := newFakeAppsets(fakeAppsetList)
client := initializeClient(appsetList)
metrics.Registry = prometheus.NewRegistry()
appsetCollector := newAppsetCollector(utils.NewAppsetLister(client), collectedLabels, filter)
metrics.Registry.MustRegister(appsetCollector)
req, err := http.NewRequest(http.MethodGet, "/metrics", nil)
require.NoError(t, err)
rr := httptest.NewRecorder()
handler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
// Test correct appset_info and owned applications
assert.Contains(t, rr.Body.String(), `
argocd_appset_info{name="test1",namespace="argocd",resource_update_status="ApplicationSetUpToDate"} 1
`)
assert.Contains(t, rr.Body.String(), `
argocd_appset_owned_applications{name="test1",namespace="argocd"} 2
`)
// Test labels collection - should not include labels not included in the list of collected labels and include the ones that do.
assert.Contains(t, rr.Body.String(), `
argocd_appset_labels{label_included_test="test",name="test1",namespace="argocd"} 1
`)
assert.NotContains(t, rr.Body.String(), normalizeLabel("not-included.label/test"))
// If collected label is not present on the applicationset the value should be empty
assert.Contains(t, rr.Body.String(), `
argocd_appset_labels{label_included_test="",name="test2",namespace="argocd"} 1
`)
// If ResourcesUpToDate condition is not present on the applicationset the status should be reported as 'Unknown'
assert.Contains(t, rr.Body.String(), `
argocd_appset_info{name="test2",namespace="argocd",resource_update_status="Unknown"} 1
`)
// If there are no resources on the applicationset the owned application gague should return 0
assert.Contains(t, rr.Body.String(), `
argocd_appset_owned_applications{name="test2",namespace="argocd"} 0
`)
// Test that filter is working
assert.NotContains(t, rr.Body.String(), `name="should-be-filtered-out"`)
}
func TestObserveReconcile(t *testing.T) {
appsetList := newFakeAppsets(fakeAppsetList)
client := initializeClient(appsetList)
metrics.Registry = prometheus.NewRegistry()
appsetMetrics := NewApplicationsetMetrics(utils.NewAppsetLister(client), collectedLabels, filter)
req, err := http.NewRequest(http.MethodGet, "/metrics", nil)
require.NoError(t, err)
rr := httptest.NewRecorder()
handler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{})
appsetMetrics.ObserveReconcile(&appsetList[0], 5*time.Second)
handler.ServeHTTP(rr, req)
assert.Contains(t, rr.Body.String(), `
argocd_appset_reconcile_sum{name="test1",namespace="argocd"} 5
`)
// If there are no resources on the applicationset the owned application gague should return 0
assert.Contains(t, rr.Body.String(), `
argocd_appset_reconcile_count{name="test1",namespace="argocd"} 1
`)
}
func initializeClient(appsets []argoappv1.ApplicationSet) ctrlclient.WithWatch {
scheme := runtime.NewScheme()
err := argoappv1.AddToScheme(scheme)
if err != nil {
panic(err)
}
var clientObjects []ctrlclient.Object
for _, appset := range appsets {
clientObjects = append(clientObjects, appset.DeepCopy())
}
return fake.NewClientBuilder().WithScheme(scheme).WithObjects(clientObjects...).Build()
}
func normalizeLabel(label string) string {
return metricsutil.NormalizeLabels("label", []string{label})[0]
}

View File

@@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"github.com/bradleyfalzon/ghinstallation/v2" "github.com/bradleyfalzon/ghinstallation/v2"
"github.com/google/go-github/v66/github" "github.com/google/go-github/v35/github"
"github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth" "github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth"
) )
@@ -26,7 +26,7 @@ func Client(g github_app_auth.Authentication, url string) (*github.Client, error
} else { } else {
rt.BaseURL = url rt.BaseURL = url
httpClient := http.Client{Transport: rt} httpClient := http.Client{Transport: rt}
client, err = github.NewClient(&httpClient).WithEnterpriseURLs(url, url) client, err = github.NewEnterpriseClient(url, url, &httpClient)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create github enterprise client: %w", err) return nil, fmt.Errorf("failed to create github enterprise client: %w", err)
} }

View File

@@ -134,7 +134,7 @@ func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*htt
// CheckResponse checks the API response for errors, and returns them if present. // CheckResponse checks the API response for errors, and returns them if present.
func CheckResponse(resp *http.Response) error { func CheckResponse(resp *http.Response) error {
if c := resp.StatusCode; http.StatusOK <= c && c < http.StatusMultipleChoices { if c := resp.StatusCode; 200 <= c && c <= 299 {
return nil return nil
} }

View File

@@ -10,7 +10,6 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestClient(t *testing.T) { func TestClient(t *testing.T) {
@@ -25,7 +24,9 @@ func TestClient(t *testing.T) {
var clientOptionFns []ClientOptionFunc var clientOptionFns []ClientOptionFunc
_, err := NewClient(server.URL, clientOptionFns...) _, err := NewClient(server.URL, clientOptionFns...)
require.NoError(t, err, "Failed to create client") if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
} }
func TestClientDo(t *testing.T) { func TestClientDo(t *testing.T) {
@@ -76,7 +77,7 @@ func TestClientDo(t *testing.T) {
"key3": float64(123), "key3": float64(123),
}, },
}, },
expectedCode: http.StatusOK, expectedCode: 200,
expectedError: nil, expectedError: nil,
}, },
{ {
@@ -108,7 +109,7 @@ func TestClientDo(t *testing.T) {
})), })),
clientOptionFns: nil, clientOptionFns: nil,
expected: []map[string]interface{}(nil), expected: []map[string]interface{}(nil),
expectedCode: http.StatusUnauthorized, expectedCode: 401,
expectedError: fmt.Errorf("API error with status code 401: "), expectedError: fmt.Errorf("API error with status code 401: "),
}, },
} { } {
@@ -117,10 +118,14 @@ func TestClientDo(t *testing.T) {
defer cc.fakeServer.Close() defer cc.fakeServer.Close()
client, err := NewClient(cc.fakeServer.URL, cc.clientOptionFns...) client, err := NewClient(cc.fakeServer.URL, cc.clientOptionFns...)
require.NoError(t, err, "NewClient returned unexpected error") if err != nil {
t.Fatalf("NewClient returned unexpected error: %v", err)
}
req, err := client.NewRequest("POST", "", cc.params, nil) req, err := client.NewRequest("POST", "", cc.params, nil)
require.NoError(t, err, "NewRequest returned unexpected error") if err != nil {
t.Fatalf("NewRequest returned unexpected error: %v", err)
}
var data []map[string]interface{} var data []map[string]interface{}
@@ -144,5 +149,12 @@ func TestCheckResponse(t *testing.T) {
} }
err := CheckResponse(resp) err := CheckResponse(resp)
require.EqualError(t, err, "API error with status code 400: invalid_request") if err == nil {
t.Error("Expected an error, got nil")
}
expected := "API error with status code 400: invalid_request"
if err.Error() != expected {
t.Errorf("Expected error '%s', got '%s'", expected, err.Error())
}
} }

View File

@@ -1,4 +1,4 @@
// Code generated by mockery v2.43.2. DO NOT EDIT. // Code generated by mockery v2.40.2. DO NOT EDIT.
package mocks package mocks

View File

@@ -9,7 +9,6 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestPlugin(t *testing.T) { func TestPlugin(t *testing.T) {
@@ -32,13 +31,19 @@ func TestPlugin(t *testing.T) {
defer ts.Close() defer ts.Close()
client, err := NewPluginService(context.Background(), "plugin-test", ts.URL, token, 0) client, err := NewPluginService(context.Background(), "plugin-test", ts.URL, token, 0)
require.NoError(t, err) if err != nil {
t.Errorf("unexpected error: %v", err)
}
data, err := client.List(context.Background(), nil) data, err := client.List(context.Background(), nil)
require.NoError(t, err) if err != nil {
t.Errorf("unexpected error: %v", err)
}
var expectedData ServiceResponse var expectedData ServiceResponse
err = json.Unmarshal([]byte(expectedJSON), &expectedData) err = json.Unmarshal([]byte(expectedJSON), &expectedData)
require.NoError(t, err) if err != nil {
t.Fatal(err)
}
assert.Equal(t, &expectedData, data) assert.Equal(t, &expectedData, data)
} }

View File

@@ -82,7 +82,6 @@ func (a *AzureDevOpsService) List(ctx context.Context) ([]*PullRequest, error) {
pr.Repository.Name == nil || pr.Repository.Name == nil ||
pr.PullRequestId == nil || pr.PullRequestId == nil ||
pr.SourceRefName == nil || pr.SourceRefName == nil ||
pr.TargetRefName == nil ||
pr.LastMergeSourceCommit == nil || pr.LastMergeSourceCommit == nil ||
pr.LastMergeSourceCommit.CommitId == nil { pr.LastMergeSourceCommit.CommitId == nil {
continue continue
@@ -95,13 +94,10 @@ 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: *pr.PullRequestId, Number: *pr.PullRequestId,
Title: *pr.Title, Branch: strings.Replace(*pr.SourceRefName, "refs/heads/", "", 1),
Branch: strings.Replace(*pr.SourceRefName, "refs/heads/", "", 1), HeadSHA: *pr.LastMergeSourceCommit.CommitId,
TargetBranch: strings.Replace(*pr.TargetRefName, "refs/heads/", "", 1), Labels: azureDevOpsLabels,
HeadSHA: *pr.LastMergeSourceCommit.CommitId,
Labels: azureDevOpsLabels,
Author: strings.Split(*pr.CreatedBy.UniqueName, "@")[0], // Get the part before the @ in the email-address
}) })
} }
} }

View File

@@ -4,8 +4,6 @@ import (
"context" "context"
"testing" "testing"
"github.com/microsoft/azure-devops-go-api/azuredevops/webapi"
"github.com/microsoft/azure-devops-go-api/azuredevops/core" "github.com/microsoft/azure-devops-go-api/azuredevops/core"
git "github.com/microsoft/azure-devops-go-api/azuredevops/git" git "github.com/microsoft/azure-devops-go-api/azuredevops/git"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -31,10 +29,6 @@ func createLabelsPtr(x []core.WebApiTagDefinition) *[]core.WebApiTagDefinition {
return &x return &x
} }
func createUniqueNamePtr(x string) *string {
return &x
}
type AzureClientFactoryMock struct { type AzureClientFactoryMock struct {
mock *mock.Mock mock *mock.Mock
} }
@@ -62,17 +56,13 @@ func TestListPullRequest(t *testing.T) {
teamProject := "myorg_project" teamProject := "myorg_project"
repoName := "myorg_project_repo" repoName := "myorg_project_repo"
pr_id := 123 pr_id := 123
pr_title := "feat(123)"
pr_head_sha := "cd4973d9d14a08ffe6b641a89a68891d6aac8056" pr_head_sha := "cd4973d9d14a08ffe6b641a89a68891d6aac8056"
ctx := context.Background() ctx := context.Background()
uniqueName := "testName"
pullRequestMock := []git.GitPullRequest{ pullRequestMock := []git.GitPullRequest{
{ {
PullRequestId: createIntPtr(pr_id), PullRequestId: createIntPtr(pr_id),
Title: createStringPtr(pr_title),
SourceRefName: createStringPtr("refs/heads/feature-branch"), SourceRefName: createStringPtr("refs/heads/feature-branch"),
TargetRefName: createStringPtr("refs/heads/main"),
LastMergeSourceCommit: &git.GitCommitRef{ LastMergeSourceCommit: &git.GitCommitRef{
CommitId: createStringPtr(pr_head_sha), CommitId: createStringPtr(pr_head_sha),
}, },
@@ -80,9 +70,6 @@ func TestListPullRequest(t *testing.T) {
Repository: &git.GitRepository{ Repository: &git.GitRepository{
Name: createStringPtr(repoName), Name: createStringPtr(repoName),
}, },
CreatedBy: &webapi.IdentityRef{
UniqueName: createUniqueNamePtr(uniqueName + "@example.com"),
},
}, },
} }
@@ -107,11 +94,8 @@ func TestListPullRequest(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, list, 1) assert.Len(t, list, 1)
assert.Equal(t, "feature-branch", list[0].Branch) assert.Equal(t, "feature-branch", list[0].Branch)
assert.Equal(t, "main", list[0].TargetBranch)
assert.Equal(t, pr_head_sha, list[0].HeadSHA) assert.Equal(t, pr_head_sha, list[0].HeadSHA)
assert.Equal(t, "feat(123)", list[0].Title)
assert.Equal(t, pr_id, list[0].Number) assert.Equal(t, pr_id, list[0].Number)
assert.Equal(t, uniqueName, list[0].Author)
} }
func TestConvertLabes(t *testing.T) { func TestConvertLabes(t *testing.T) {

View File

@@ -17,9 +17,7 @@ type BitbucketCloudService struct {
type BitbucketCloudPullRequest struct { type BitbucketCloudPullRequest struct {
ID int `json:"id"` ID int `json:"id"`
Title string `json:"title"`
Source BitbucketCloudPullRequestSource `json:"source"` Source BitbucketCloudPullRequestSource `json:"source"`
Author BitbucketCloudPullRequestAuthor `json:"author"`
} }
type BitbucketCloudPullRequestSource struct { type BitbucketCloudPullRequestSource struct {
@@ -35,11 +33,6 @@ type BitbucketCloudPullRequestSourceCommit struct {
Hash string `json:"hash"` Hash string `json:"hash"`
} }
// Also have display_name and uuid, but don't plan to use them.
type BitbucketCloudPullRequestAuthor struct {
Nickname string `json:"nickname"`
}
type PullRequestResponse struct { type PullRequestResponse struct {
Page int32 `json:"page"` Page int32 `json:"page"`
Size int32 `json:"size"` Size int32 `json:"size"`
@@ -136,10 +129,8 @@ func (b *BitbucketCloudService) List(_ context.Context) ([]*PullRequest, error)
for _, pull := range pulls { for _, pull := range pulls {
pullRequests = append(pullRequests, &PullRequest{ pullRequests = append(pullRequests, &PullRequest{
Number: pull.ID, Number: pull.ID,
Title: pull.Title,
Branch: pull.Source.Branch.Name, Branch: pull.Source.Branch.Name,
HeadSHA: pull.Source.Commit.Hash, HeadSHA: pull.Source.Commit.Hash,
Author: pull.Author.Nickname,
}) })
} }

View File

@@ -15,7 +15,6 @@ import (
) )
func defaultHandlerCloud(t *testing.T) func(http.ResponseWriter, *http.Request) { func defaultHandlerCloud(t *testing.T) func(http.ResponseWriter, *http.Request) {
t.Helper()
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
var err error var err error
@@ -28,7 +27,6 @@ func defaultHandlerCloud(t *testing.T) func(http.ResponseWriter, *http.Request)
"values": [ "values": [
{ {
"id": 101, "id": 101,
"title": "feat(foo-bar)",
"source": { "source": {
"branch": { "branch": {
"name": "feature/foo-bar" "name": "feature/foo-bar"
@@ -37,9 +35,6 @@ func defaultHandlerCloud(t *testing.T) func(http.ResponseWriter, *http.Request)
"type": "commit", "type": "commit",
"hash": "1a8dd249c04a" "hash": "1a8dd249c04a"
} }
},
"author": {
"nickname": "testName"
} }
} }
] ]
@@ -91,10 +86,8 @@ func TestListPullRequestBearerTokenCloud(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, 101, pullRequests[0].Number) assert.Equal(t, 101, pullRequests[0].Number)
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)
assert.Equal(t, "testName", pullRequests[0].Author)
} }
func TestListPullRequestNoAuthCloud(t *testing.T) { func TestListPullRequestNoAuthCloud(t *testing.T) {
@@ -109,10 +102,8 @@ func TestListPullRequestNoAuthCloud(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, 101, pullRequests[0].Number) assert.Equal(t, 101, pullRequests[0].Number)
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)
assert.Equal(t, "testName", pullRequests[0].Author)
} }
func TestListPullRequestBasicAuthCloud(t *testing.T) { func TestListPullRequestBasicAuthCloud(t *testing.T) {
@@ -127,10 +118,8 @@ func TestListPullRequestBasicAuthCloud(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, 101, pullRequests[0].Number) assert.Equal(t, 101, pullRequests[0].Number)
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)
assert.Equal(t, "testName", pullRequests[0].Author)
} }
func TestListPullRequestPaginationCloud(t *testing.T) { func TestListPullRequestPaginationCloud(t *testing.T) {
@@ -147,7 +136,6 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
"values": [ "values": [
{ {
"id": 101, "id": 101,
"title": "feat(101)",
"source": { "source": {
"branch": { "branch": {
"name": "feature-101" "name": "feature-101"
@@ -156,14 +144,10 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
"type": "commit", "type": "commit",
"hash": "1a8dd249c04a" "hash": "1a8dd249c04a"
} }
},
"author": {
"nickname": "testName"
} }
}, },
{ {
"id": 102, "id": 102,
"title": "feat(102)",
"source": { "source": {
"branch": { "branch": {
"name": "feature-102" "name": "feature-102"
@@ -172,9 +156,6 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
"type": "commit", "type": "commit",
"hash": "4cf807e67a6d" "hash": "4cf807e67a6d"
} }
},
"author": {
"nickname": "testName"
} }
} }
] ]
@@ -188,7 +169,6 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
"values": [ "values": [
{ {
"id": 103, "id": 103,
"title": "feat(103)",
"source": { "source": {
"branch": { "branch": {
"name": "feature-103" "name": "feature-103"
@@ -197,9 +177,6 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
"type": "commit", "type": "commit",
"hash": "6344d9623e3b" "hash": "6344d9623e3b"
} }
},
"author": {
"nickname": "testName"
} }
} }
] ]
@@ -219,30 +196,24 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
assert.Len(t, pullRequests, 3) assert.Len(t, pullRequests, 3)
assert.Equal(t, PullRequest{ assert.Equal(t, PullRequest{
Number: 101, Number: 101,
Title: "feat(101)",
Branch: "feature-101", Branch: "feature-101",
HeadSHA: "1a8dd249c04a", HeadSHA: "1a8dd249c04a",
Author: "testName",
}, *pullRequests[0]) }, *pullRequests[0])
assert.Equal(t, PullRequest{ assert.Equal(t, PullRequest{
Number: 102, Number: 102,
Title: "feat(102)",
Branch: "feature-102", Branch: "feature-102",
HeadSHA: "4cf807e67a6d", HeadSHA: "4cf807e67a6d",
Author: "testName",
}, *pullRequests[1]) }, *pullRequests[1])
assert.Equal(t, PullRequest{ assert.Equal(t, PullRequest{
Number: 103, Number: 103,
Title: "feat(103)",
Branch: "feature-103", Branch: "feature-103",
HeadSHA: "6344d9623e3b", HeadSHA: "6344d9623e3b",
Author: "testName",
}, *pullRequests[2]) }, *pullRequests[2])
} }
func TestListResponseErrorCloud(t *testing.T) { func TestListResponseErrorCloud(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) {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(500)
})) }))
defer ts.Close() defer ts.Close()
svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO") svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
@@ -338,7 +309,6 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
"values": [ "values": [
{ {
"id": 101, "id": 101,
"title": "feat(101)",
"source": { "source": {
"branch": { "branch": {
"name": "feature-101" "name": "feature-101"
@@ -347,14 +317,10 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
"type": "commit", "type": "commit",
"hash": "1a8dd249c04a" "hash": "1a8dd249c04a"
} }
},
"author": {
"nickname": "testName"
} }
}, },
{ {
"id": 200, "id": 200,
"title": "feat(200)",
"source": { "source": {
"branch": { "branch": {
"name": "feature-200" "name": "feature-200"
@@ -363,9 +329,6 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
"type": "commit", "type": "commit",
"hash": "4cf807e67a6d" "hash": "4cf807e67a6d"
} }
},
"author": {
"nickname": "testName"
} }
} }
] ]
@@ -379,7 +342,6 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
"values": [ "values": [
{ {
"id": 102, "id": 102,
"title": "feat(102)",
"source": { "source": {
"branch": { "branch": {
"name": "feature-102" "name": "feature-102"
@@ -388,9 +350,6 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
"type": "commit", "type": "commit",
"hash": "6344d9623e3b" "hash": "6344d9623e3b"
} }
},
"author": {
"nickname": "testName"
} }
} }
] ]
@@ -415,17 +374,13 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
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)",
Branch: "feature-101", Branch: "feature-101",
HeadSHA: "1a8dd249c04a", HeadSHA: "1a8dd249c04a",
Author: "testName",
}, *pullRequests[0]) }, *pullRequests[0])
assert.Equal(t, PullRequest{ assert.Equal(t, PullRequest{
Number: 102, Number: 102,
Title: "feat(102)",
Branch: "feature-102", Branch: "feature-102",
HeadSHA: "6344d9623e3b", HeadSHA: "6344d9623e3b",
Author: "testName",
}, *pullRequests[1]) }, *pullRequests[1])
regexp = `.*2$` regexp = `.*2$`
@@ -440,10 +395,8 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
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)",
Branch: "feature-102", Branch: "feature-102",
HeadSHA: "6344d9623e3b", HeadSHA: "6344d9623e3b",
Author: "testName",
}, *pullRequests[0]) }, *pullRequests[0])
regexp = `[\d{2}` regexp = `[\d{2}`

View File

@@ -3,7 +3,6 @@ package pull_request
import ( import (
"context" "context"
"fmt" "fmt"
"net/http"
bitbucketv1 "github.com/gfleury/go-bitbucket-v1" bitbucketv1 "github.com/gfleury/go-bitbucket-v1"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@@ -21,7 +20,7 @@ type BitbucketService struct {
var _ PullRequestService = (*BitbucketService)(nil) var _ PullRequestService = (*BitbucketService)(nil)
func NewBitbucketServiceBasicAuth(ctx context.Context, username, password, url, projectKey, repositorySlug string, scmRootCAPath string, insecure bool, caCerts []byte) (PullRequestService, error) { func NewBitbucketServiceBasicAuth(ctx context.Context, username, password, url, projectKey, repositorySlug string) (PullRequestService, error) {
bitbucketConfig := bitbucketv1.NewConfiguration(url) bitbucketConfig := bitbucketv1.NewConfiguration(url)
// Avoid the XSRF check // Avoid the XSRF check
bitbucketConfig.AddDefaultHeader("x-atlassian-token", "no-check") bitbucketConfig.AddDefaultHeader("x-atlassian-token", "no-check")
@@ -31,29 +30,15 @@ func NewBitbucketServiceBasicAuth(ctx context.Context, username, password, url,
UserName: username, UserName: username,
Password: password, Password: password,
}) })
return newBitbucketService(ctx, bitbucketConfig, projectKey, repositorySlug, scmRootCAPath, insecure, caCerts) return newBitbucketService(ctx, bitbucketConfig, projectKey, repositorySlug)
} }
func NewBitbucketServiceBearerToken(ctx context.Context, bearerToken, url, projectKey, repositorySlug string, scmRootCAPath string, insecure bool, caCerts []byte) (PullRequestService, error) { func NewBitbucketServiceNoAuth(ctx context.Context, url, projectKey, repositorySlug string) (PullRequestService, error) {
bitbucketConfig := bitbucketv1.NewConfiguration(url) return newBitbucketService(ctx, bitbucketv1.NewConfiguration(url), projectKey, repositorySlug)
// Avoid the XSRF check
bitbucketConfig.AddDefaultHeader("x-atlassian-token", "no-check")
bitbucketConfig.AddDefaultHeader("x-requested-with", "XMLHttpRequest")
ctx = context.WithValue(ctx, bitbucketv1.ContextAccessToken, bearerToken)
return newBitbucketService(ctx, bitbucketConfig, projectKey, repositorySlug, scmRootCAPath, insecure, caCerts)
} }
func NewBitbucketServiceNoAuth(ctx context.Context, url, projectKey, repositorySlug string, scmRootCAPath string, insecure bool, caCerts []byte) (PullRequestService, error) { func newBitbucketService(ctx context.Context, bitbucketConfig *bitbucketv1.Configuration, projectKey, repositorySlug string) (PullRequestService, error) {
return newBitbucketService(ctx, bitbucketv1.NewConfiguration(url), projectKey, repositorySlug, scmRootCAPath, insecure, caCerts)
}
func newBitbucketService(ctx context.Context, bitbucketConfig *bitbucketv1.Configuration, projectKey, repositorySlug string, scmRootCAPath string, insecure bool, caCerts []byte) (PullRequestService, error) {
bitbucketConfig.BasePath = utils.NormalizeBitbucketBasePath(bitbucketConfig.BasePath) 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) bitbucketClient := bitbucketv1.NewAPIClient(ctx, bitbucketConfig)
return &BitbucketService{ return &BitbucketService{
@@ -83,12 +68,10 @@ 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: pull.ID, Number: pull.ID,
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,
HeadSHA: pull.FromRef.LatestCommit, // This is not defined in the official docs, but works in practice HeadSHA: pull.FromRef.LatestCommit, // This is not defined in the official docs, but works in practice
Labels: []string{}, // Not supported by library Labels: []string{}, // Not supported by library
Author: pull.Author.User.Name,
}) })
} }

View File

@@ -2,8 +2,6 @@ package pull_request
import ( import (
"context" "context"
"crypto/x509"
"encoding/pem"
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@@ -16,7 +14,6 @@ import (
) )
func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) { func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
t.Helper()
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
var err error var err error
@@ -29,7 +26,6 @@ func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
"values": [ "values": [
{ {
"id": 101, "id": 101,
"title": "feat(ABC) : 123",
"toRef": { "toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a", "latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "master", "displayId": "master",
@@ -39,11 +35,6 @@ func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
"id": "refs/heads/feature-ABC-123", "id": "refs/heads/feature-ABC-123",
"displayId": "feature-ABC-123", "displayId": "feature-ABC-123",
"latestCommit": "cb3cf2e4d1517c83e720d2585b9402dbef71f992" "latestCommit": "cb3cf2e4d1517c83e720d2585b9402dbef71f992"
},
"author": {
"user": {
"name": "testName"
}
} }
} }
], ],
@@ -64,17 +55,15 @@ func TestListPullRequestNoAuth(t *testing.T) {
defaultHandler(t)(w, r) defaultHandler(t)(w, r)
})) }))
defer ts.Close() defer ts.Close()
svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO", "", false, nil) svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
require.NoError(t, err) require.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, 101, pullRequests[0].Number) assert.Equal(t, 101, pullRequests[0].Number)
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)
assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA) assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA)
assert.Equal(t, "testName", pullRequests[0].Author)
} }
func TestListPullRequestPagination(t *testing.T) { func TestListPullRequestPagination(t *testing.T) {
@@ -90,7 +79,6 @@ func TestListPullRequestPagination(t *testing.T) {
"values": [ "values": [
{ {
"id": 101, "id": 101,
"title": "feat(101)",
"toRef": { "toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a", "latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "master", "displayId": "master",
@@ -100,16 +88,10 @@ func TestListPullRequestPagination(t *testing.T) {
"id": "refs/heads/feature-101", "id": "refs/heads/feature-101",
"displayId": "feature-101", "displayId": "feature-101",
"latestCommit": "ab3cf2e4d1517c83e720d2585b9402dbef71f992" "latestCommit": "ab3cf2e4d1517c83e720d2585b9402dbef71f992"
},
"author": {
"user": {
"name": "testName"
}
} }
}, },
{ {
"id": 102, "id": 102,
"title": "feat(102)",
"toRef": { "toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a", "latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "branch", "displayId": "branch",
@@ -119,11 +101,6 @@ func TestListPullRequestPagination(t *testing.T) {
"id": "refs/heads/feature-102", "id": "refs/heads/feature-102",
"displayId": "feature-102", "displayId": "feature-102",
"latestCommit": "bb3cf2e4d1517c83e720d2585b9402dbef71f992" "latestCommit": "bb3cf2e4d1517c83e720d2585b9402dbef71f992"
},
"author": {
"user": {
"name": "testName"
}
} }
} }
], ],
@@ -137,7 +114,6 @@ func TestListPullRequestPagination(t *testing.T) {
"values": [ "values": [
{ {
"id": 200, "id": 200,
"title": "feat(200)",
"toRef": { "toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a", "latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "master", "displayId": "master",
@@ -147,11 +123,6 @@ func TestListPullRequestPagination(t *testing.T) {
"id": "refs/heads/feature-200", "id": "refs/heads/feature-200",
"displayId": "feature-200", "displayId": "feature-200",
"latestCommit": "cb3cf2e4d1517c83e720d2585b9402dbef71f992" "latestCommit": "cb3cf2e4d1517c83e720d2585b9402dbef71f992"
},
"author": {
"user": {
"name": "testName"
}
} }
} }
], ],
@@ -165,37 +136,31 @@ func TestListPullRequestPagination(t *testing.T) {
} }
})) }))
defer ts.Close() defer ts.Close()
svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO", "", false, nil) svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
require.NoError(t, err) require.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 3) assert.Len(t, pullRequests, 3)
assert.Equal(t, PullRequest{ assert.Equal(t, PullRequest{
Number: 101, Number: 101,
Title: "feat(101)",
Branch: "feature-101", Branch: "feature-101",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "ab3cf2e4d1517c83e720d2585b9402dbef71f992", HeadSHA: "ab3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{}, Labels: []string{},
Author: "testName",
}, *pullRequests[0]) }, *pullRequests[0])
assert.Equal(t, PullRequest{ assert.Equal(t, PullRequest{
Number: 102, Number: 102,
Title: "feat(102)",
Branch: "feature-102", Branch: "feature-102",
TargetBranch: "branch", TargetBranch: "branch",
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992", HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{}, Labels: []string{},
Author: "testName",
}, *pullRequests[1]) }, *pullRequests[1])
assert.Equal(t, PullRequest{ assert.Equal(t, PullRequest{
Number: 200, Number: 200,
Title: "feat(200)",
Branch: "feature-200", Branch: "feature-200",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "cb3cf2e4d1517c83e720d2585b9402dbef71f992", HeadSHA: "cb3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{}, Labels: []string{},
Author: "testName",
}, *pullRequests[2]) }, *pullRequests[2])
} }
@@ -207,7 +172,7 @@ func TestListPullRequestBasicAuth(t *testing.T) {
defaultHandler(t)(w, r) defaultHandler(t)(w, r)
})) }))
defer ts.Close() defer ts.Close()
svc, err := NewBitbucketServiceBasicAuth(context.Background(), "user", "password", ts.URL, "PROJECT", "REPO", "", false, nil) svc, err := NewBitbucketServiceBasicAuth(context.Background(), "user", "password", ts.URL, "PROJECT", "REPO")
require.NoError(t, err) require.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
@@ -217,97 +182,12 @@ func TestListPullRequestBasicAuth(t *testing.T) {
assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA) assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA)
} }
func TestListPullRequestBearerAuth(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "Bearer tolkien", r.Header.Get("Authorization"))
assert.Equal(t, "no-check", r.Header.Get("X-Atlassian-Token"))
defaultHandler(t)(w, r)
}))
defer ts.Close()
svc, err := NewBitbucketServiceBearerToken(context.Background(), "tolkien", ts.URL, "PROJECT", "REPO", "", false, nil)
require.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feat(ABC) : 123", pullRequests[0].Title)
assert.Equal(t, "feature-ABC-123", pullRequests[0].Branch)
assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA)
}
func TestListPullRequestTLS(t *testing.T) {
tests := []struct {
name string
tlsInsecure bool
passCerts bool
requireErr bool
}{
{
name: "TLS Insecure: true, No Certs",
tlsInsecure: true,
passCerts: false,
requireErr: false,
},
{
name: "TLS Insecure: true, With Certs",
tlsInsecure: true,
passCerts: true,
requireErr: false,
},
{
name: "TLS Insecure: false, With Certs",
tlsInsecure: false,
passCerts: true,
requireErr: false,
},
{
name: "TLS Insecure: false, No Certs",
tlsInsecure: false,
passCerts: false,
requireErr: true,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defaultHandler(t)(w, r)
}))
defer ts.Close()
var certs []byte
if test.passCerts == true {
for _, cert := range ts.TLS.Certificates {
for _, c := range cert.Certificate {
parsedCert, err := x509.ParseCertificate(c)
require.NoError(t, err, "Failed to parse certificate")
certs = append(certs, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: parsedCert.Raw,
})...)
}
}
}
svc, err := NewBitbucketServiceBasicAuth(context.Background(), "user", "password", ts.URL, "PROJECT", "REPO", "", test.tlsInsecure, certs)
require.NoError(t, err)
_, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
if test.requireErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
func TestListResponseError(t *testing.T) { func TestListResponseError(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) {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
})) }))
defer ts.Close() defer ts.Close()
svc, _ := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO", "", false, nil) svc, _ := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{}) _, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.Error(t, err) require.Error(t, err)
} }
@@ -332,7 +212,7 @@ func TestListResponseMalformed(t *testing.T) {
} }
})) }))
defer ts.Close() defer ts.Close()
svc, _ := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO", "", false, nil) svc, _ := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{}) _, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.Error(t, err) require.Error(t, err)
} }
@@ -357,7 +237,7 @@ func TestListResponseEmpty(t *testing.T) {
} }
})) }))
defer ts.Close() defer ts.Close()
svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO", "", false, nil) svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
require.NoError(t, err) require.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
@@ -377,7 +257,6 @@ func TestListPullRequestBranchMatch(t *testing.T) {
"values": [ "values": [
{ {
"id": 101, "id": 101,
"title": "feat(101)",
"toRef": { "toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a", "latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "master", "displayId": "master",
@@ -387,16 +266,10 @@ func TestListPullRequestBranchMatch(t *testing.T) {
"id": "refs/heads/feature-101", "id": "refs/heads/feature-101",
"displayId": "feature-101", "displayId": "feature-101",
"latestCommit": "ab3cf2e4d1517c83e720d2585b9402dbef71f992" "latestCommit": "ab3cf2e4d1517c83e720d2585b9402dbef71f992"
},
"author": {
"user": {
"name": "testName"
}
} }
}, },
{ {
"id": 102, "id": 102,
"title": "feat(102)",
"toRef": { "toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a", "latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "branch", "displayId": "branch",
@@ -406,11 +279,6 @@ func TestListPullRequestBranchMatch(t *testing.T) {
"id": "refs/heads/feature-102", "id": "refs/heads/feature-102",
"displayId": "feature-102", "displayId": "feature-102",
"latestCommit": "bb3cf2e4d1517c83e720d2585b9402dbef71f992" "latestCommit": "bb3cf2e4d1517c83e720d2585b9402dbef71f992"
},
"author": {
"user": {
"name": "testName"
}
} }
} }
], ],
@@ -424,7 +292,6 @@ func TestListPullRequestBranchMatch(t *testing.T) {
"values": [ "values": [
{ {
"id": 200, "id": 200,
"title": "feat(200)",
"toRef": { "toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a", "latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "master", "displayId": "master",
@@ -434,11 +301,6 @@ func TestListPullRequestBranchMatch(t *testing.T) {
"id": "refs/heads/feature-200", "id": "refs/heads/feature-200",
"displayId": "feature-200", "displayId": "feature-200",
"latestCommit": "cb3cf2e4d1517c83e720d2585b9402dbef71f992" "latestCommit": "cb3cf2e4d1517c83e720d2585b9402dbef71f992"
},
"author": {
"user": {
"name": "testName"
}
} }
} }
], ],
@@ -453,7 +315,7 @@ func TestListPullRequestBranchMatch(t *testing.T) {
})) }))
defer ts.Close() defer ts.Close()
regexp := `feature-1[\d]{2}` regexp := `feature-1[\d]{2}`
svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO", "", false, nil) svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
require.NoError(t, err) require.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{ pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{ {
@@ -464,25 +326,21 @@ func TestListPullRequestBranchMatch(t *testing.T) {
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)",
Branch: "feature-101", Branch: "feature-101",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "ab3cf2e4d1517c83e720d2585b9402dbef71f992", HeadSHA: "ab3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{}, Labels: []string{},
Author: "testName",
}, *pullRequests[0]) }, *pullRequests[0])
assert.Equal(t, PullRequest{ assert.Equal(t, PullRequest{
Number: 102, Number: 102,
Title: "feat(102)",
Branch: "feature-102", Branch: "feature-102",
TargetBranch: "branch", TargetBranch: "branch",
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992", HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{}, Labels: []string{},
Author: "testName",
}, *pullRequests[1]) }, *pullRequests[1])
regexp = `.*2$` regexp = `.*2$`
svc, err = NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO", "", false, nil) svc, err = NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
require.NoError(t, err) require.NoError(t, err)
pullRequests, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{ pullRequests, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{ {
@@ -493,16 +351,14 @@ func TestListPullRequestBranchMatch(t *testing.T) {
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)",
Branch: "feature-102", Branch: "feature-102",
TargetBranch: "branch", TargetBranch: "branch",
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992", HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{}, Labels: []string{},
Author: "testName",
}, *pullRequests[0]) }, *pullRequests[0])
regexp = `[\d{2}` regexp = `[\d{2}`
svc, err = NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO", "", false, nil) svc, err = NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
require.NoError(t, err) require.NoError(t, err)
_, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{ _, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{ {

View File

@@ -57,12 +57,10 @@ func (g *GiteaService) List(ctx context.Context) ([]*PullRequest, error) {
for _, pr := range prs { for _, pr := range prs {
list = append(list, &PullRequest{ list = append(list, &PullRequest{
Number: int(pr.Index), Number: int(pr.Index),
Title: pr.Title,
Branch: pr.Head.Ref, Branch: pr.Head.Ref,
TargetBranch: pr.Base.Ref, TargetBranch: pr.Base.Ref,
HeadSHA: pr.Head.Sha, HeadSHA: pr.Head.Sha,
Labels: getGiteaPRLabelNames(pr.Labels), Labels: getGiteaPRLabelNames(pr.Labels),
Author: pr.Poster.UserName,
}) })
} }
return list, nil return list, nil

View File

@@ -14,7 +14,6 @@ import (
) )
func giteaMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) { func giteaMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
t.Helper()
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
fmt.Println(r.RequestURI) fmt.Println(r.RequestURI)
@@ -257,11 +256,9 @@ func TestGiteaList(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, prs, 1) assert.Len(t, prs, 1)
assert.Equal(t, 1, prs[0].Number) assert.Equal(t, 1, prs[0].Number)
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)
assert.Equal(t, "7bbaf62d92ddfafd9cc8b340c619abaec32bc09f", prs[0].HeadSHA) assert.Equal(t, "7bbaf62d92ddfafd9cc8b340c619abaec32bc09f", prs[0].HeadSHA)
assert.Equal(t, "graytshirt", prs[0].Author)
} }
func TestGetGiteaPRLabelNames(t *testing.T) { func TestGetGiteaPRLabelNames(t *testing.T) {

View File

@@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/google/go-github/v66/github" "github.com/google/go-github/v35/github"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
@@ -35,7 +35,7 @@ func NewGithubService(ctx context.Context, token, url, owner, repo string, label
client = github.NewClient(httpClient) client = github.NewClient(httpClient)
} else { } else {
var err error var err error
client, err = github.NewClient(httpClient).WithEnterpriseURLs(url, url) client, err = github.NewEnterpriseClient(url, url, httpClient)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -66,12 +66,10 @@ func (g *GithubService) List(ctx context.Context) ([]*PullRequest, error) {
} }
pullRequests = append(pullRequests, &PullRequest{ pullRequests = append(pullRequests, &PullRequest{
Number: *pull.Number, Number: *pull.Number,
Title: *pull.Title,
Branch: *pull.Head.Ref, Branch: *pull.Head.Ref,
TargetBranch: *pull.Base.Ref, TargetBranch: *pull.Base.Ref,
HeadSHA: *pull.Head.SHA, HeadSHA: *pull.Head.SHA,
Labels: getGithubPRLabelNames(pull.Labels), Labels: getGithubPRLabelNames(pull.Labels),
Author: *pull.User.Login,
}) })
} }
if resp.NextPage == 0 { if resp.NextPage == 0 {

View File

@@ -3,8 +3,8 @@ package pull_request
import ( import (
"testing" "testing"
"github.com/google/go-github/v66/github" "github.com/google/go-github/v35/github"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/assert"
) )
func toPtr(s string) *string { func toPtr(s string) *string {
@@ -52,8 +52,9 @@ func TestContainLabels(t *testing.T) {
for _, c := range cases { for _, c := range cases {
t.Run(c.Name, func(t *testing.T) { t.Run(c.Name, func(t *testing.T) {
got := containLabels(c.Labels, c.PullLabels) if got := containLabels(c.Labels, c.PullLabels); got != c.Expect {
require.Equal(t, got, c.Expect) t.Errorf("expect: %v, got: %v", c.Expect, got)
}
}) })
} }
} }
@@ -82,7 +83,7 @@ func TestGetGitHubPRLabelNames(t *testing.T) {
for _, test := range Tests { for _, test := range Tests {
t.Run(test.Name, func(t *testing.T) { t.Run(test.Name, func(t *testing.T) {
labels := getGithubPRLabelNames(test.PullLabels) labels := getGithubPRLabelNames(test.PullLabels)
require.Equal(t, test.ExpectedResult, labels) assert.Equal(t, test.ExpectedResult, labels)
}) })
} }
} }

View File

@@ -21,7 +21,7 @@ type GitLabService struct {
var _ PullRequestService = (*GitLabService)(nil) var _ PullRequestService = (*GitLabService)(nil)
func NewGitLabService(ctx context.Context, token, url, project string, labels []string, pullRequestState string, scmRootCAPath string, insecure bool, caCerts []byte) (PullRequestService, error) { func NewGitLabService(ctx context.Context, token, url, project string, labels []string, pullRequestState string, scmRootCAPath string, insecure bool) (PullRequestService, error) {
var clientOptionFns []gitlab.ClientOptionFunc var clientOptionFns []gitlab.ClientOptionFunc
// Set a custom Gitlab base URL if one is provided // Set a custom Gitlab base URL if one is provided
@@ -34,7 +34,7 @@ func NewGitLabService(ctx context.Context, token, url, project string, labels []
} }
tr := http.DefaultTransport.(*http.Transport).Clone() tr := http.DefaultTransport.(*http.Transport).Clone()
tr.TLSClientConfig = utils.GetTlsConfig(scmRootCAPath, insecure, caCerts) tr.TLSClientConfig = utils.GetTlsConfig(scmRootCAPath, insecure)
retryClient := retryablehttp.NewClient() retryClient := retryablehttp.NewClient()
retryClient.HTTPClient.Transport = tr retryClient.HTTPClient.Transport = tr
@@ -56,11 +56,11 @@ func NewGitLabService(ctx context.Context, token, url, project string, labels []
func (g *GitLabService) List(ctx context.Context) ([]*PullRequest, error) { func (g *GitLabService) List(ctx context.Context) ([]*PullRequest, error) {
// Filter the merge requests on labels, if they are specified. // Filter the merge requests on labels, if they are specified.
var labels *gitlab.LabelOptions var labels *gitlab.Labels
if len(g.labels) > 0 { if len(g.labels) > 0 {
var labelsList gitlab.LabelOptions = g.labels labels = (*gitlab.Labels)(&g.labels)
labels = &labelsList
} }
opts := &gitlab.ListProjectMergeRequestsOptions{ opts := &gitlab.ListProjectMergeRequestsOptions{
ListOptions: gitlab.ListOptions{ ListOptions: gitlab.ListOptions{
PerPage: 100, PerPage: 100,
@@ -81,12 +81,10 @@ func (g *GitLabService) List(ctx context.Context) ([]*PullRequest, error) {
for _, mr := range mrs { for _, mr := range mrs {
pullRequests = append(pullRequests, &PullRequest{ pullRequests = append(pullRequests, &PullRequest{
Number: mr.IID, Number: mr.IID,
Title: mr.Title,
Branch: mr.SourceBranch, Branch: mr.SourceBranch,
TargetBranch: mr.TargetBranch, TargetBranch: mr.TargetBranch,
HeadSHA: mr.SHA, HeadSHA: mr.SHA,
Labels: mr.Labels, Labels: mr.Labels,
Author: mr.Author.Username,
}) })
} }
if resp.NextPage == 0 { if resp.NextPage == 0 {

View File

@@ -2,8 +2,6 @@ package pull_request
import ( import (
"context" "context"
"crypto/x509"
"encoding/pem"
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@@ -15,12 +13,14 @@ import (
) )
func writeMRListResponse(t *testing.T, w io.Writer) { func writeMRListResponse(t *testing.T, w io.Writer) {
t.Helper()
f, err := os.Open("fixtures/gitlab_mr_list_response.json") f, err := os.Open("fixtures/gitlab_mr_list_response.json")
require.NoErrorf(t, err, "error opening fixture file: %v", err) if err != nil {
t.Fatalf("error opening fixture file: %v", err)
}
_, err = io.Copy(w, f) if _, err = io.Copy(w, f); err != nil {
require.NoErrorf(t, err, "error writing response: %v", err) t.Fatalf("error writing response: %v", err)
}
} }
func TestGitLabServiceCustomBaseURL(t *testing.T) { func TestGitLabServiceCustomBaseURL(t *testing.T) {
@@ -35,7 +35,7 @@ func TestGitLabServiceCustomBaseURL(t *testing.T) {
writeMRListResponse(t, w) writeMRListResponse(t, w)
}) })
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", nil, "", "", false, nil) svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", nil, "", "", false)
require.NoError(t, err) require.NoError(t, err)
_, err = svc.List(context.Background()) _, err = svc.List(context.Background())
@@ -54,7 +54,7 @@ func TestGitLabServiceToken(t *testing.T) {
writeMRListResponse(t, w) writeMRListResponse(t, w)
}) })
svc, err := NewGitLabService(context.Background(), "token-123", server.URL, "278964", nil, "", "", false, nil) svc, err := NewGitLabService(context.Background(), "token-123", server.URL, "278964", nil, "", "", false)
require.NoError(t, err) require.NoError(t, err)
_, err = svc.List(context.Background()) _, err = svc.List(context.Background())
@@ -73,18 +73,16 @@ func TestList(t *testing.T) {
writeMRListResponse(t, w) writeMRListResponse(t, w)
}) })
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "", "", false, nil) svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "", "", false)
require.NoError(t, err) require.NoError(t, err)
prs, err := svc.List(context.Background()) prs, err := svc.List(context.Background())
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, prs, 1) assert.Len(t, prs, 1)
assert.Equal(t, 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, "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)
assert.Equal(t, "2fc4e8b972ff3208ec63b6143e34ad67ff343ad7", prs[0].HeadSHA) assert.Equal(t, "2fc4e8b972ff3208ec63b6143e34ad67ff343ad7", prs[0].HeadSHA)
assert.Equal(t, "hfyngvason", prs[0].Author)
} }
func TestListWithLabels(t *testing.T) { func TestListWithLabels(t *testing.T) {
@@ -99,7 +97,7 @@ func TestListWithLabels(t *testing.T) {
writeMRListResponse(t, w) writeMRListResponse(t, w)
}) })
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{"feature", "ready"}, "", "", false, nil) svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{"feature", "ready"}, "", "", false)
require.NoError(t, err) require.NoError(t, err)
_, err = svc.List(context.Background()) _, err = svc.List(context.Background())
@@ -118,77 +116,9 @@ func TestListWithState(t *testing.T) {
writeMRListResponse(t, w) writeMRListResponse(t, w)
}) })
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "opened", "", false, nil) svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "opened", "", false)
require.NoError(t, err) require.NoError(t, err)
_, err = svc.List(context.Background()) _, err = svc.List(context.Background())
require.NoError(t, err) require.NoError(t, err)
} }
func TestListWithStateTLS(t *testing.T) {
tests := []struct {
name string
tlsInsecure bool
passCerts bool
requireErr bool
}{
{
name: "TLS Insecure: true, No Certs",
tlsInsecure: true,
passCerts: false,
requireErr: false,
},
{
name: "TLS Insecure: true, With Certs",
tlsInsecure: true,
passCerts: true,
requireErr: false,
},
{
name: "TLS Insecure: false, With Certs",
tlsInsecure: false,
passCerts: true,
requireErr: false,
},
{
name: "TLS Insecure: false, No Certs",
tlsInsecure: false,
passCerts: false,
requireErr: true,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
writeMRListResponse(t, w)
}))
defer ts.Close()
var certs []byte
if test.passCerts == true {
for _, cert := range ts.TLS.Certificates {
for _, c := range cert.Certificate {
parsedCert, err := x509.ParseCertificate(c)
require.NoError(t, err, "Failed to parse certificate")
certs = append(certs, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: parsedCert.Raw,
})...)
}
}
}
svc, err := NewGitLabService(context.Background(), "", ts.URL, "278964", []string{}, "opened", "", test.tlsInsecure, certs)
require.NoError(t, err)
_, err = svc.List(context.Background())
if test.requireErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}

View File

@@ -8,8 +8,6 @@ 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.
Number int Number int
// Title of the pull request.
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.
Branch string Branch string
// TargetBranch is the name of the target branch of the pull request. // TargetBranch is the name of the target branch of the pull request.
@@ -18,8 +16,6 @@ type PullRequest struct {
HeadSHA string HeadSHA string
// Labels of the pull request. // Labels of the pull request.
Labels []string Labels []string
// Author is the author of the pull request.
Author string
} }
type PullRequestService interface { type PullRequestService interface {

View File

@@ -20,11 +20,9 @@ func TestFilterBranchMatchBadRegexp(t *testing.T) {
[]*PullRequest{ []*PullRequest{
{ {
Number: 1, Number: 1,
Title: "PR branch1",
Branch: "branch1", Branch: "branch1",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name1",
}, },
}, },
nil, nil,
@@ -44,35 +42,27 @@ func TestFilterBranchMatch(t *testing.T) {
[]*PullRequest{ []*PullRequest{
{ {
Number: 1, Number: 1,
Title: "PR one",
Branch: "one", Branch: "one",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name1",
}, },
{ {
Number: 2, Number: 2,
Title: "PR two",
Branch: "two", Branch: "two",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name2",
}, },
{ {
Number: 3, Number: 3,
Title: "PR three",
Branch: "three", Branch: "three",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name3",
}, },
{ {
Number: 4, Number: 4,
Title: "PR four",
Branch: "four", Branch: "four",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name4",
}, },
}, },
nil, nil,
@@ -94,35 +84,27 @@ func TestFilterTargetBranchMatch(t *testing.T) {
[]*PullRequest{ []*PullRequest{
{ {
Number: 1, Number: 1,
Title: "PR one",
Branch: "one", Branch: "one",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name1",
}, },
{ {
Number: 2, Number: 2,
Title: "PR two",
Branch: "two", Branch: "two",
TargetBranch: "branch1", TargetBranch: "branch1",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name2",
}, },
{ {
Number: 3, Number: 3,
Title: "PR three",
Branch: "three", Branch: "three",
TargetBranch: "branch2", TargetBranch: "branch2",
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name3",
}, },
{ {
Number: 4, Number: 4,
Title: "PR four",
Branch: "four", Branch: "four",
TargetBranch: "branch3", TargetBranch: "branch3",
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name4",
}, },
}, },
nil, nil,
@@ -144,35 +126,27 @@ func TestMultiFilterOr(t *testing.T) {
[]*PullRequest{ []*PullRequest{
{ {
Number: 1, Number: 1,
Title: "PR one",
Branch: "one", Branch: "one",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name1",
}, },
{ {
Number: 2, Number: 2,
Title: "PR two",
Branch: "two", Branch: "two",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name2",
}, },
{ {
Number: 3, Number: 3,
Title: "PR three",
Branch: "three", Branch: "three",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name3",
}, },
{ {
Number: 4, Number: 4,
Title: "PR four",
Branch: "four", Branch: "four",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name4",
}, },
}, },
nil, nil,
@@ -199,35 +173,27 @@ func TestMultiFilterOrWithTargetBranchFilter(t *testing.T) {
[]*PullRequest{ []*PullRequest{
{ {
Number: 1, Number: 1,
Title: "PR one",
Branch: "one", Branch: "one",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name1",
}, },
{ {
Number: 2, Number: 2,
Title: "PR two",
Branch: "two", Branch: "two",
TargetBranch: "branch1", TargetBranch: "branch1",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name2",
}, },
{ {
Number: 3, Number: 3,
Title: "PR three",
Branch: "three", Branch: "three",
TargetBranch: "branch2", TargetBranch: "branch2",
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name3",
}, },
{ {
Number: 4, Number: 4,
Title: "PR four",
Branch: "four", Branch: "four",
TargetBranch: "branch3", TargetBranch: "branch3",
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name4",
}, },
}, },
nil, nil,
@@ -255,19 +221,15 @@ func TestNoFilters(t *testing.T) {
[]*PullRequest{ []*PullRequest{
{ {
Number: 1, Number: 1,
Title: "PR one",
Branch: "one", Branch: "one",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name1",
}, },
{ {
Number: 2, Number: 2,
Title: "PR two",
Branch: "two", Branch: "two",
TargetBranch: "master", TargetBranch: "master",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958", HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name2",
}, },
}, },
nil, nil,

View File

@@ -18,6 +18,8 @@ type argoCDService struct {
newFileGlobbingEnabled bool newFileGlobbingEnabled bool
} }
//go:generate go run github.com/vektra/mockery/v2@v2.40.2 --name=Repos
type Repos interface { type Repos interface {
// GetFiles returns content of files (not directories) within the target repo // GetFiles returns content of files (not directories) within the target repo
GetFiles(ctx context.Context, repoURL string, revision string, pattern string, noRevisionCache, verifyCommit bool) (map[string][]byte, error) GetFiles(ctx context.Context, repoURL string, revision string, pattern string, noRevisionCache, verifyCommit bool) (map[string][]byte, error)

View File

@@ -191,6 +191,6 @@ func TestNewArgoCDService(t *testing.T) {
service, err := NewArgoCDService(func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) { service, err := NewArgoCDService(func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) {
return &v1alpha1.Repository{}, nil return &v1alpha1.Repository{}, nil
}, false, &repo_mocks.Clientset{}, false) }, false, &repo_mocks.Clientset{}, false)
require.NoError(t, err) require.NoError(t, err, err)
assert.NotNil(t, service) assert.NotNil(t, service)
} }

View File

@@ -1,4 +1,4 @@
// Code generated by mockery v2.43.2. DO NOT EDIT. // Code generated by mockery v2.26.1. DO NOT EDIT.
package mocks package mocks
@@ -17,6 +17,14 @@ type AWSCodeCommitClient struct {
mock.Mock mock.Mock
} }
type AWSCodeCommitClient_Expecter struct {
mock *mock.Mock
}
func (_m *AWSCodeCommitClient) EXPECT() *AWSCodeCommitClient_Expecter {
return &AWSCodeCommitClient_Expecter{mock: &_m.Mock}
}
// GetFolderWithContext provides a mock function with given fields: _a0, _a1, _a2 // GetFolderWithContext provides a mock function with given fields: _a0, _a1, _a2
func (_m *AWSCodeCommitClient) GetFolderWithContext(_a0 context.Context, _a1 *codecommit.GetFolderInput, _a2 ...request.Option) (*codecommit.GetFolderOutput, error) { func (_m *AWSCodeCommitClient) GetFolderWithContext(_a0 context.Context, _a1 *codecommit.GetFolderInput, _a2 ...request.Option) (*codecommit.GetFolderOutput, error) {
_va := make([]interface{}, len(_a2)) _va := make([]interface{}, len(_a2))
@@ -28,10 +36,6 @@ func (_m *AWSCodeCommitClient) GetFolderWithContext(_a0 context.Context, _a1 *co
_ca = append(_ca, _va...) _ca = append(_ca, _va...)
ret := _m.Called(_ca...) ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for GetFolderWithContext")
}
var r0 *codecommit.GetFolderOutput var r0 *codecommit.GetFolderOutput
var r1 error var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.GetFolderInput, ...request.Option) (*codecommit.GetFolderOutput, error)); ok { if rf, ok := ret.Get(0).(func(context.Context, *codecommit.GetFolderInput, ...request.Option) (*codecommit.GetFolderOutput, error)); ok {
@@ -54,6 +58,43 @@ func (_m *AWSCodeCommitClient) GetFolderWithContext(_a0 context.Context, _a1 *co
return r0, r1 return r0, r1
} }
// AWSCodeCommitClient_GetFolderWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFolderWithContext'
type AWSCodeCommitClient_GetFolderWithContext_Call struct {
*mock.Call
}
// GetFolderWithContext is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *codecommit.GetFolderInput
// - _a2 ...request.Option
func (_e *AWSCodeCommitClient_Expecter) GetFolderWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSCodeCommitClient_GetFolderWithContext_Call {
return &AWSCodeCommitClient_GetFolderWithContext_Call{Call: _e.mock.On("GetFolderWithContext",
append([]interface{}{_a0, _a1}, _a2...)...)}
}
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) Run(run func(_a0 context.Context, _a1 *codecommit.GetFolderInput, _a2 ...request.Option)) *AWSCodeCommitClient_GetFolderWithContext_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]request.Option, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
}
}
run(args[0].(context.Context), args[1].(*codecommit.GetFolderInput), variadicArgs...)
})
return _c
}
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) Return(_a0 *codecommit.GetFolderOutput, _a1 error) *AWSCodeCommitClient_GetFolderWithContext_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) RunAndReturn(run func(context.Context, *codecommit.GetFolderInput, ...request.Option) (*codecommit.GetFolderOutput, error)) *AWSCodeCommitClient_GetFolderWithContext_Call {
_c.Call.Return(run)
return _c
}
// GetRepositoryWithContext provides a mock function with given fields: _a0, _a1, _a2 // GetRepositoryWithContext provides a mock function with given fields: _a0, _a1, _a2
func (_m *AWSCodeCommitClient) GetRepositoryWithContext(_a0 context.Context, _a1 *codecommit.GetRepositoryInput, _a2 ...request.Option) (*codecommit.GetRepositoryOutput, error) { func (_m *AWSCodeCommitClient) GetRepositoryWithContext(_a0 context.Context, _a1 *codecommit.GetRepositoryInput, _a2 ...request.Option) (*codecommit.GetRepositoryOutput, error) {
_va := make([]interface{}, len(_a2)) _va := make([]interface{}, len(_a2))
@@ -65,10 +106,6 @@ func (_m *AWSCodeCommitClient) GetRepositoryWithContext(_a0 context.Context, _a1
_ca = append(_ca, _va...) _ca = append(_ca, _va...)
ret := _m.Called(_ca...) ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for GetRepositoryWithContext")
}
var r0 *codecommit.GetRepositoryOutput var r0 *codecommit.GetRepositoryOutput
var r1 error var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.GetRepositoryInput, ...request.Option) (*codecommit.GetRepositoryOutput, error)); ok { if rf, ok := ret.Get(0).(func(context.Context, *codecommit.GetRepositoryInput, ...request.Option) (*codecommit.GetRepositoryOutput, error)); ok {
@@ -91,6 +128,43 @@ func (_m *AWSCodeCommitClient) GetRepositoryWithContext(_a0 context.Context, _a1
return r0, r1 return r0, r1
} }
// AWSCodeCommitClient_GetRepositoryWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRepositoryWithContext'
type AWSCodeCommitClient_GetRepositoryWithContext_Call struct {
*mock.Call
}
// GetRepositoryWithContext is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *codecommit.GetRepositoryInput
// - _a2 ...request.Option
func (_e *AWSCodeCommitClient_Expecter) GetRepositoryWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
return &AWSCodeCommitClient_GetRepositoryWithContext_Call{Call: _e.mock.On("GetRepositoryWithContext",
append([]interface{}{_a0, _a1}, _a2...)...)}
}
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) Run(run func(_a0 context.Context, _a1 *codecommit.GetRepositoryInput, _a2 ...request.Option)) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]request.Option, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
}
}
run(args[0].(context.Context), args[1].(*codecommit.GetRepositoryInput), variadicArgs...)
})
return _c
}
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) Return(_a0 *codecommit.GetRepositoryOutput, _a1 error) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) RunAndReturn(run func(context.Context, *codecommit.GetRepositoryInput, ...request.Option) (*codecommit.GetRepositoryOutput, error)) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
_c.Call.Return(run)
return _c
}
// ListBranchesWithContext provides a mock function with given fields: _a0, _a1, _a2 // ListBranchesWithContext provides a mock function with given fields: _a0, _a1, _a2
func (_m *AWSCodeCommitClient) ListBranchesWithContext(_a0 context.Context, _a1 *codecommit.ListBranchesInput, _a2 ...request.Option) (*codecommit.ListBranchesOutput, error) { func (_m *AWSCodeCommitClient) ListBranchesWithContext(_a0 context.Context, _a1 *codecommit.ListBranchesInput, _a2 ...request.Option) (*codecommit.ListBranchesOutput, error) {
_va := make([]interface{}, len(_a2)) _va := make([]interface{}, len(_a2))
@@ -102,10 +176,6 @@ func (_m *AWSCodeCommitClient) ListBranchesWithContext(_a0 context.Context, _a1
_ca = append(_ca, _va...) _ca = append(_ca, _va...)
ret := _m.Called(_ca...) ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for ListBranchesWithContext")
}
var r0 *codecommit.ListBranchesOutput var r0 *codecommit.ListBranchesOutput
var r1 error var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.ListBranchesInput, ...request.Option) (*codecommit.ListBranchesOutput, error)); ok { if rf, ok := ret.Get(0).(func(context.Context, *codecommit.ListBranchesInput, ...request.Option) (*codecommit.ListBranchesOutput, error)); ok {
@@ -128,6 +198,43 @@ func (_m *AWSCodeCommitClient) ListBranchesWithContext(_a0 context.Context, _a1
return r0, r1 return r0, r1
} }
// AWSCodeCommitClient_ListBranchesWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListBranchesWithContext'
type AWSCodeCommitClient_ListBranchesWithContext_Call struct {
*mock.Call
}
// ListBranchesWithContext is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *codecommit.ListBranchesInput
// - _a2 ...request.Option
func (_e *AWSCodeCommitClient_Expecter) ListBranchesWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSCodeCommitClient_ListBranchesWithContext_Call {
return &AWSCodeCommitClient_ListBranchesWithContext_Call{Call: _e.mock.On("ListBranchesWithContext",
append([]interface{}{_a0, _a1}, _a2...)...)}
}
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) Run(run func(_a0 context.Context, _a1 *codecommit.ListBranchesInput, _a2 ...request.Option)) *AWSCodeCommitClient_ListBranchesWithContext_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]request.Option, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
}
}
run(args[0].(context.Context), args[1].(*codecommit.ListBranchesInput), variadicArgs...)
})
return _c
}
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) Return(_a0 *codecommit.ListBranchesOutput, _a1 error) *AWSCodeCommitClient_ListBranchesWithContext_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) RunAndReturn(run func(context.Context, *codecommit.ListBranchesInput, ...request.Option) (*codecommit.ListBranchesOutput, error)) *AWSCodeCommitClient_ListBranchesWithContext_Call {
_c.Call.Return(run)
return _c
}
// ListRepositoriesWithContext provides a mock function with given fields: _a0, _a1, _a2 // ListRepositoriesWithContext provides a mock function with given fields: _a0, _a1, _a2
func (_m *AWSCodeCommitClient) ListRepositoriesWithContext(_a0 context.Context, _a1 *codecommit.ListRepositoriesInput, _a2 ...request.Option) (*codecommit.ListRepositoriesOutput, error) { func (_m *AWSCodeCommitClient) ListRepositoriesWithContext(_a0 context.Context, _a1 *codecommit.ListRepositoriesInput, _a2 ...request.Option) (*codecommit.ListRepositoriesOutput, error) {
_va := make([]interface{}, len(_a2)) _va := make([]interface{}, len(_a2))
@@ -139,10 +246,6 @@ func (_m *AWSCodeCommitClient) ListRepositoriesWithContext(_a0 context.Context,
_ca = append(_ca, _va...) _ca = append(_ca, _va...)
ret := _m.Called(_ca...) ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for ListRepositoriesWithContext")
}
var r0 *codecommit.ListRepositoriesOutput var r0 *codecommit.ListRepositoriesOutput
var r1 error var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.ListRepositoriesInput, ...request.Option) (*codecommit.ListRepositoriesOutput, error)); ok { if rf, ok := ret.Get(0).(func(context.Context, *codecommit.ListRepositoriesInput, ...request.Option) (*codecommit.ListRepositoriesOutput, error)); ok {
@@ -165,12 +268,50 @@ func (_m *AWSCodeCommitClient) ListRepositoriesWithContext(_a0 context.Context,
return r0, r1 return r0, r1
} }
// NewAWSCodeCommitClient creates a new instance of AWSCodeCommitClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // AWSCodeCommitClient_ListRepositoriesWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListRepositoriesWithContext'
// The first argument is typically a *testing.T value. type AWSCodeCommitClient_ListRepositoriesWithContext_Call struct {
func NewAWSCodeCommitClient(t interface { *mock.Call
}
// ListRepositoriesWithContext is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *codecommit.ListRepositoriesInput
// - _a2 ...request.Option
func (_e *AWSCodeCommitClient_Expecter) ListRepositoriesWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
return &AWSCodeCommitClient_ListRepositoriesWithContext_Call{Call: _e.mock.On("ListRepositoriesWithContext",
append([]interface{}{_a0, _a1}, _a2...)...)}
}
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) Run(run func(_a0 context.Context, _a1 *codecommit.ListRepositoriesInput, _a2 ...request.Option)) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]request.Option, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
}
}
run(args[0].(context.Context), args[1].(*codecommit.ListRepositoriesInput), variadicArgs...)
})
return _c
}
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) Return(_a0 *codecommit.ListRepositoriesOutput, _a1 error) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) RunAndReturn(run func(context.Context, *codecommit.ListRepositoriesInput, ...request.Option) (*codecommit.ListRepositoriesOutput, error)) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
_c.Call.Return(run)
return _c
}
type mockConstructorTestingTNewAWSCodeCommitClient interface {
mock.TestingT mock.TestingT
Cleanup(func()) Cleanup(func())
}) *AWSCodeCommitClient { }
// NewAWSCodeCommitClient creates a new instance of AWSCodeCommitClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewAWSCodeCommitClient(t mockConstructorTestingTNewAWSCodeCommitClient) *AWSCodeCommitClient {
mock := &AWSCodeCommitClient{} mock := &AWSCodeCommitClient{}
mock.Mock.Test(t) mock.Mock.Test(t)

View File

@@ -1,4 +1,4 @@
// Code generated by mockery v2.43.2. DO NOT EDIT. // Code generated by mockery v2.26.1. DO NOT EDIT.
package mocks package mocks
@@ -16,6 +16,14 @@ type AWSTaggingClient struct {
mock.Mock mock.Mock
} }
type AWSTaggingClient_Expecter struct {
mock *mock.Mock
}
func (_m *AWSTaggingClient) EXPECT() *AWSTaggingClient_Expecter {
return &AWSTaggingClient_Expecter{mock: &_m.Mock}
}
// GetResourcesWithContext provides a mock function with given fields: _a0, _a1, _a2 // GetResourcesWithContext provides a mock function with given fields: _a0, _a1, _a2
func (_m *AWSTaggingClient) GetResourcesWithContext(_a0 context.Context, _a1 *resourcegroupstaggingapi.GetResourcesInput, _a2 ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error) { func (_m *AWSTaggingClient) GetResourcesWithContext(_a0 context.Context, _a1 *resourcegroupstaggingapi.GetResourcesInput, _a2 ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error) {
_va := make([]interface{}, len(_a2)) _va := make([]interface{}, len(_a2))
@@ -27,10 +35,6 @@ func (_m *AWSTaggingClient) GetResourcesWithContext(_a0 context.Context, _a1 *re
_ca = append(_ca, _va...) _ca = append(_ca, _va...)
ret := _m.Called(_ca...) ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for GetResourcesWithContext")
}
var r0 *resourcegroupstaggingapi.GetResourcesOutput var r0 *resourcegroupstaggingapi.GetResourcesOutput
var r1 error var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error)); ok { if rf, ok := ret.Get(0).(func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error)); ok {
@@ -53,12 +57,50 @@ func (_m *AWSTaggingClient) GetResourcesWithContext(_a0 context.Context, _a1 *re
return r0, r1 return r0, r1
} }
// NewAWSTaggingClient creates a new instance of AWSTaggingClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // AWSTaggingClient_GetResourcesWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetResourcesWithContext'
// The first argument is typically a *testing.T value. type AWSTaggingClient_GetResourcesWithContext_Call struct {
func NewAWSTaggingClient(t interface { *mock.Call
}
// GetResourcesWithContext is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *resourcegroupstaggingapi.GetResourcesInput
// - _a2 ...request.Option
func (_e *AWSTaggingClient_Expecter) GetResourcesWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSTaggingClient_GetResourcesWithContext_Call {
return &AWSTaggingClient_GetResourcesWithContext_Call{Call: _e.mock.On("GetResourcesWithContext",
append([]interface{}{_a0, _a1}, _a2...)...)}
}
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) Run(run func(_a0 context.Context, _a1 *resourcegroupstaggingapi.GetResourcesInput, _a2 ...request.Option)) *AWSTaggingClient_GetResourcesWithContext_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]request.Option, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
}
}
run(args[0].(context.Context), args[1].(*resourcegroupstaggingapi.GetResourcesInput), variadicArgs...)
})
return _c
}
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) Return(_a0 *resourcegroupstaggingapi.GetResourcesOutput, _a1 error) *AWSTaggingClient_GetResourcesWithContext_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) RunAndReturn(run func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error)) *AWSTaggingClient_GetResourcesWithContext_Call {
_c.Call.Return(run)
return _c
}
type mockConstructorTestingTNewAWSTaggingClient interface {
mock.TestingT mock.TestingT
Cleanup(func()) Cleanup(func())
}) *AWSTaggingClient { }
// NewAWSTaggingClient creates a new instance of AWSTaggingClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewAWSTaggingClient(t mockConstructorTestingTNewAWSTaggingClient) *AWSTaggingClient {
mock := &AWSTaggingClient{} mock := &AWSTaggingClient{}
mock.Mock.Test(t) mock.Mock.Test(t)

View File

@@ -178,8 +178,8 @@ func TestAWSCodeCommitListRepos(t *testing.T) {
if repo.getRepositoryNilMetadata { if repo.getRepositoryNilMetadata {
repoMetadata = nil repoMetadata = nil
} }
codeCommitClient. codeCommitClient.EXPECT().
On("GetRepositoryWithContext", ctx, &codecommit.GetRepositoryInput{RepositoryName: aws.String(repo.name)}). GetRepositoryWithContext(ctx, &codecommit.GetRepositoryInput{RepositoryName: aws.String(repo.name)}).
Return(&codecommit.GetRepositoryOutput{RepositoryMetadata: repoMetadata}, repo.getRepositoryError) Return(&codecommit.GetRepositoryOutput{RepositoryMetadata: repoMetadata}, repo.getRepositoryError)
codecommitRepoNameIdPairs = append(codecommitRepoNameIdPairs, &codecommit.RepositoryNameIdPair{ codecommitRepoNameIdPairs = append(codecommitRepoNameIdPairs, &codecommit.RepositoryNameIdPair{
RepositoryId: aws.String(repo.id), RepositoryId: aws.String(repo.id),
@@ -194,14 +194,14 @@ func TestAWSCodeCommitListRepos(t *testing.T) {
} }
if testCase.expectListAtCodeCommit { if testCase.expectListAtCodeCommit {
codeCommitClient. codeCommitClient.EXPECT().
On("ListRepositoriesWithContext", ctx, &codecommit.ListRepositoriesInput{}). ListRepositoriesWithContext(ctx, &codecommit.ListRepositoriesInput{}).
Return(&codecommit.ListRepositoriesOutput{ Return(&codecommit.ListRepositoriesOutput{
Repositories: codecommitRepoNameIdPairs, Repositories: codecommitRepoNameIdPairs,
}, testCase.listRepositoryError) }, testCase.listRepositoryError)
} else { } else {
taggingClient. taggingClient.EXPECT().
On("GetResourcesWithContext", ctx, mock.MatchedBy(equalIgnoringTagFilterOrder(&resourcegroupstaggingapi.GetResourcesInput{ GetResourcesWithContext(ctx, mock.MatchedBy(equalIgnoringTagFilterOrder(&resourcegroupstaggingapi.GetResourcesInput{
TagFilters: testCase.expectTagFilters, TagFilters: testCase.expectTagFilters,
ResourceTypeFilters: aws.StringSlice([]string{resourceTypeCodeCommitRepository}), ResourceTypeFilters: aws.StringSlice([]string{resourceTypeCodeCommitRepository}),
}))). }))).
@@ -351,8 +351,8 @@ func TestAWSCodeCommitRepoHasPath(t *testing.T) {
taggingClient := mocks.NewAWSTaggingClient(t) taggingClient := mocks.NewAWSTaggingClient(t)
ctx := context.Background() ctx := context.Background()
if testCase.expectedGetFolderPath != "" { if testCase.expectedGetFolderPath != "" {
codeCommitClient. codeCommitClient.EXPECT().
On("GetFolderWithContext", ctx, &codecommit.GetFolderInput{ GetFolderWithContext(ctx, &codecommit.GetFolderInput{
CommitSpecifier: aws.String(branch), CommitSpecifier: aws.String(branch),
FolderPath: aws.String(testCase.expectedGetFolderPath), FolderPath: aws.String(testCase.expectedGetFolderPath),
RepositoryName: aws.String(repoName), RepositoryName: aws.String(repoName),
@@ -424,14 +424,14 @@ func TestAWSCodeCommitGetBranches(t *testing.T) {
taggingClient := mocks.NewAWSTaggingClient(t) taggingClient := mocks.NewAWSTaggingClient(t)
ctx := context.Background() ctx := context.Background()
if testCase.allBranches { if testCase.allBranches {
codeCommitClient. codeCommitClient.EXPECT().
On("ListBranchesWithContext", ctx, &codecommit.ListBranchesInput{ ListBranchesWithContext(ctx, &codecommit.ListBranchesInput{
RepositoryName: aws.String(name), RepositoryName: aws.String(name),
}). }).
Return(&codecommit.ListBranchesOutput{Branches: aws.StringSlice(testCase.branches)}, testCase.apiError) Return(&codecommit.ListBranchesOutput{Branches: aws.StringSlice(testCase.branches)}, testCase.apiError)
} else { } else {
codeCommitClient. codeCommitClient.EXPECT().
On("GetRepositoryWithContext", ctx, &codecommit.GetRepositoryInput{RepositoryName: aws.String(name)}). GetRepositoryWithContext(ctx, &codecommit.GetRepositoryInput{RepositoryName: aws.String(name)}).
Return(&codecommit.GetRepositoryOutput{RepositoryMetadata: &codecommit.RepositoryMetadata{ Return(&codecommit.GetRepositoryOutput{RepositoryMetadata: &codecommit.RepositoryMetadata{
AccountId: aws.String(organization), AccountId: aws.String(organization),
DefaultBranch: aws.String(defaultBranch), DefaultBranch: aws.String(defaultBranch),

View File

@@ -1,4 +1,4 @@
// Code generated by mockery v2.43.2. DO NOT EDIT. // Code generated by mockery v2.40.2. DO NOT EDIT.
package mocks package mocks

View File

@@ -17,6 +17,8 @@ import (
azureMock "github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider/azure_devops/git/mocks" azureMock "github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider/azure_devops/git/mocks"
) )
//go:generate go run github.com/vektra/mockery/v2@v2.40.2 --srcpkg=github.com/microsoft/azure-devops-go-api/azuredevops/git --name=Client --output=azure_devops/git/mocks --outpkg=mocks
func s(input string) *string { func s(input string) *string {
return ptr.To(input) return ptr.To(input)
} }

View File

@@ -46,7 +46,7 @@ func (c *ExtendedClient) GetContents(repo *Repository, path string) (bool, error
return true, nil return true, nil
} }
return false, fmt.Errorf("%s", resp.Status) return false, fmt.Errorf(resp.Status)
} }
var _ SCMProviderService = &BitBucketCloudProvider{} var _ SCMProviderService = &BitBucketCloudProvider{}

View File

@@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net/http"
bitbucketv1 "github.com/gfleury/go-bitbucket-v1" bitbucketv1 "github.com/gfleury/go-bitbucket-v1"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@@ -21,7 +20,7 @@ type BitbucketServerProvider struct {
var _ SCMProviderService = &BitbucketServerProvider{} var _ SCMProviderService = &BitbucketServerProvider{}
func NewBitbucketServerProviderBasicAuth(ctx context.Context, username, password, url, projectKey string, allBranches bool, scmRootCAPath string, insecure bool, caCerts []byte) (*BitbucketServerProvider, error) { func NewBitbucketServerProviderBasicAuth(ctx context.Context, username, password, url, projectKey string, allBranches bool) (*BitbucketServerProvider, error) {
bitbucketConfig := bitbucketv1.NewConfiguration(url) bitbucketConfig := bitbucketv1.NewConfiguration(url)
// Avoid the XSRF check // Avoid the XSRF check
bitbucketConfig.AddDefaultHeader("x-atlassian-token", "no-check") bitbucketConfig.AddDefaultHeader("x-atlassian-token", "no-check")
@@ -31,29 +30,15 @@ func NewBitbucketServerProviderBasicAuth(ctx context.Context, username, password
UserName: username, UserName: username,
Password: password, Password: password,
}) })
return newBitbucketServerProvider(ctx, bitbucketConfig, projectKey, allBranches, scmRootCAPath, insecure, caCerts) return newBitbucketServerProvider(ctx, bitbucketConfig, projectKey, allBranches)
} }
func NewBitbucketServerProviderBearerToken(ctx context.Context, bearerToken, url, projectKey string, allBranches bool, scmRootCAPath string, insecure bool, caCerts []byte) (*BitbucketServerProvider, error) { func NewBitbucketServerProviderNoAuth(ctx context.Context, url, projectKey string, allBranches bool) (*BitbucketServerProvider, error) {
bitbucketConfig := bitbucketv1.NewConfiguration(url) return newBitbucketServerProvider(ctx, bitbucketv1.NewConfiguration(url), projectKey, allBranches)
// Avoid the XSRF check
bitbucketConfig.AddDefaultHeader("x-atlassian-token", "no-check")
bitbucketConfig.AddDefaultHeader("x-requested-with", "XMLHttpRequest")
ctx = context.WithValue(ctx, bitbucketv1.ContextAccessToken, bearerToken)
return newBitbucketServerProvider(ctx, bitbucketConfig, projectKey, allBranches, scmRootCAPath, insecure, caCerts)
} }
func NewBitbucketServerProviderNoAuth(ctx context.Context, url, projectKey string, allBranches bool, scmRootCAPath string, insecure bool, caCerts []byte) (*BitbucketServerProvider, error) { func newBitbucketServerProvider(ctx context.Context, bitbucketConfig *bitbucketv1.Configuration, projectKey string, allBranches bool) (*BitbucketServerProvider, error) {
return newBitbucketServerProvider(ctx, bitbucketv1.NewConfiguration(url), projectKey, allBranches, scmRootCAPath, insecure, caCerts)
}
func newBitbucketServerProvider(ctx context.Context, bitbucketConfig *bitbucketv1.Configuration, projectKey string, allBranches bool, scmRootCAPath string, insecure bool, caCerts []byte) (*BitbucketServerProvider, error) {
bitbucketConfig.BasePath = utils.NormalizeBitbucketBasePath(bitbucketConfig.BasePath) 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) bitbucketClient := bitbucketv1.NewAPIClient(ctx, bitbucketConfig)
return &BitbucketServerProvider{ return &BitbucketServerProvider{
@@ -129,7 +114,7 @@ func (b *BitbucketServerProvider) RepoHasPath(_ context.Context, repo *Repositor
} }
// No need to query for all pages here // No need to query for all pages here
response, err := b.client.DefaultApi.GetContent_0(repo.Organization, repo.Repository, path, opts) response, err := b.client.DefaultApi.GetContent_0(repo.Organization, repo.Repository, path, opts)
if response != nil && response.StatusCode == http.StatusNotFound { if response != nil && response.StatusCode == 404 {
// File/directory not found // File/directory not found
return false, nil return false, nil
} }
@@ -203,7 +188,7 @@ func (b *BitbucketServerProvider) getDefaultBranch(org string, repo string) (*bi
response, err := b.client.DefaultApi.GetDefaultBranch(org, repo) response, err := b.client.DefaultApi.GetDefaultBranch(org, repo)
// The API will return 404 if a default branch is set but doesn't exist. In case the repo is empty and default branch is unset, // The API will return 404 if a default branch is set but doesn't exist. In case the repo is empty and default branch is unset,
// we will get an EOF and a nil response. // we will get an EOF and a nil response.
if (response != nil && response.StatusCode == http.StatusNotFound) || (response == nil && err != nil && errors.Is(err, io.EOF)) { if (response != nil && response.StatusCode == 404) || (response == nil && err != nil && errors.Is(err, io.EOF)) {
return nil, nil return nil, nil
} }
if err != nil { if err != nil {

View File

@@ -2,8 +2,6 @@ package scm_provider
import ( import (
"context" "context"
"crypto/x509"
"encoding/pem"
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@@ -14,7 +12,6 @@ import (
) )
func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) { func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
t.Helper()
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
var err error var err error
@@ -83,7 +80,6 @@ func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
} }
func verifyDefaultRepo(t *testing.T, err error, repos []*Repository) { func verifyDefaultRepo(t *testing.T, err error, repos []*Repository) {
t.Helper()
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, repos, 1) assert.Len(t, repos, 1)
assert.Equal(t, Repository{ assert.Equal(t, Repository{
@@ -103,7 +99,7 @@ func TestListReposNoAuth(t *testing.T) {
defaultHandler(t)(w, r) defaultHandler(t)(w, r)
})) }))
defer ts.Close() defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true, "", false, nil) provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true)
require.NoError(t, err) require.NoError(t, err)
repos, err := provider.ListRepos(context.Background(), "ssh") repos, err := provider.ListRepos(context.Background(), "ssh")
verifyDefaultRepo(t, err, repos) verifyDefaultRepo(t, err, repos)
@@ -195,7 +191,7 @@ func TestListReposPagination(t *testing.T) {
} }
})) }))
defer ts.Close() defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true, "", false, nil) provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true)
require.NoError(t, err) require.NoError(t, err)
repos, err := provider.ListRepos(context.Background(), "ssh") repos, err := provider.ListRepos(context.Background(), "ssh")
require.NoError(t, err) require.NoError(t, err)
@@ -272,7 +268,7 @@ func TestGetBranchesBranchPagination(t *testing.T) {
defaultHandler(t)(w, r) defaultHandler(t)(w, r)
})) }))
defer ts.Close() defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true, "", false, nil) provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true)
require.NoError(t, err) require.NoError(t, err)
repos, err := provider.GetBranches(context.Background(), &Repository{ repos, err := provider.GetBranches(context.Background(), &Repository{
Organization: "PROJECT", Organization: "PROJECT",
@@ -325,7 +321,7 @@ func TestGetBranchesDefaultOnly(t *testing.T) {
defaultHandler(t)(w, r) defaultHandler(t)(w, r)
})) }))
defer ts.Close() defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false, "", false, nil) provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
require.NoError(t, err) require.NoError(t, err)
repos, err := provider.GetBranches(context.Background(), &Repository{ repos, err := provider.GetBranches(context.Background(), &Repository{
Organization: "PROJECT", Organization: "PROJECT",
@@ -357,7 +353,7 @@ func TestGetBranchesMissingDefault(t *testing.T) {
defaultHandler(t)(w, r) defaultHandler(t)(w, r)
})) }))
defer ts.Close() defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false, "", false, nil) provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
require.NoError(t, err) require.NoError(t, err)
repos, err := provider.GetBranches(context.Background(), &Repository{ repos, err := provider.GetBranches(context.Background(), &Repository{
Organization: "PROJECT", Organization: "PROJECT",
@@ -379,7 +375,7 @@ func TestGetBranchesEmptyRepo(t *testing.T) {
} }
})) }))
defer ts.Close() defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false, "", false, nil) provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
require.NoError(t, err) require.NoError(t, err)
repos, err := provider.GetBranches(context.Background(), &Repository{ repos, err := provider.GetBranches(context.Background(), &Repository{
Organization: "PROJECT", Organization: "PROJECT",
@@ -402,7 +398,7 @@ func TestGetBranchesErrorDefaultBranch(t *testing.T) {
defaultHandler(t)(w, r) defaultHandler(t)(w, r)
})) }))
defer ts.Close() defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false, "", false, nil) provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
require.NoError(t, err) require.NoError(t, err)
_, err = provider.GetBranches(context.Background(), &Repository{ _, err = provider.GetBranches(context.Background(), &Repository{
Organization: "PROJECT", Organization: "PROJECT",
@@ -414,73 +410,6 @@ func TestGetBranchesErrorDefaultBranch(t *testing.T) {
require.Error(t, err) require.Error(t, err)
} }
func TestListReposTLS(t *testing.T) {
tests := []struct {
name string
tlsInsecure bool
passCerts bool
requireErr bool
}{
{
name: "TLS Insecure: true, No Certs",
tlsInsecure: true,
passCerts: false,
requireErr: false,
},
{
name: "TLS Insecure: true, With Certs",
tlsInsecure: true,
passCerts: true,
requireErr: false,
},
{
name: "TLS Insecure: false, With Certs",
tlsInsecure: false,
passCerts: true,
requireErr: false,
},
{
name: "TLS Insecure: false, No Certs",
tlsInsecure: false,
passCerts: false,
requireErr: true,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defaultHandler(t)(w, r)
}))
defer ts.Close()
var certs []byte
if test.passCerts == true {
for _, cert := range ts.TLS.Certificates {
for _, c := range cert.Certificate {
parsedCert, err := x509.ParseCertificate(c)
require.NoError(t, err, "Failed to parse certificate")
certs = append(certs, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: parsedCert.Raw,
})...)
}
}
}
provider, err := NewBitbucketServerProviderBasicAuth(context.Background(), "user", "password", ts.URL, "PROJECT", true, "", test.tlsInsecure, certs)
require.NoError(t, err)
_, err = provider.ListRepos(context.Background(), "ssh")
if test.requireErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
func TestListReposBasicAuth(t *testing.T) { func TestListReposBasicAuth(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) {
assert.Equal(t, "Basic dXNlcjpwYXNzd29yZA==", r.Header.Get("Authorization")) assert.Equal(t, "Basic dXNlcjpwYXNzd29yZA==", r.Header.Get("Authorization"))
@@ -488,20 +417,7 @@ func TestListReposBasicAuth(t *testing.T) {
defaultHandler(t)(w, r) defaultHandler(t)(w, r)
})) }))
defer ts.Close() defer ts.Close()
provider, err := NewBitbucketServerProviderBasicAuth(context.Background(), "user", "password", ts.URL, "PROJECT", true, "", false, nil) provider, err := NewBitbucketServerProviderBasicAuth(context.Background(), "user", "password", ts.URL, "PROJECT", true)
require.NoError(t, err)
repos, err := provider.ListRepos(context.Background(), "ssh")
verifyDefaultRepo(t, err, repos)
}
func TestListReposBearerAuth(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "Bearer tolkien", r.Header.Get("Authorization"))
assert.Equal(t, "no-check", r.Header.Get("X-Atlassian-Token"))
defaultHandler(t)(w, r)
}))
defer ts.Close()
provider, err := NewBitbucketServerProviderBearerToken(context.Background(), "tolkien", ts.URL, "PROJECT", true, "", false, nil)
require.NoError(t, err) require.NoError(t, err)
repos, err := provider.ListRepos(context.Background(), "ssh") repos, err := provider.ListRepos(context.Background(), "ssh")
verifyDefaultRepo(t, err, repos) verifyDefaultRepo(t, err, repos)
@@ -528,7 +444,7 @@ func TestListReposDefaultBranch(t *testing.T) {
defaultHandler(t)(w, r) defaultHandler(t)(w, r)
})) }))
defer ts.Close() defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false, "", false, nil) provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
require.NoError(t, err) require.NoError(t, err)
repos, err := provider.ListRepos(context.Background(), "ssh") repos, err := provider.ListRepos(context.Background(), "ssh")
require.NoError(t, err) require.NoError(t, err)
@@ -554,7 +470,7 @@ func TestListReposMissingDefaultBranch(t *testing.T) {
defaultHandler(t)(w, r) defaultHandler(t)(w, r)
})) }))
defer ts.Close() defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false, "", false, nil) provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
require.NoError(t, err) require.NoError(t, err)
repos, err := provider.ListRepos(context.Background(), "ssh") repos, err := provider.ListRepos(context.Background(), "ssh")
require.NoError(t, err) require.NoError(t, err)
@@ -571,7 +487,7 @@ func TestListReposErrorDefaultBranch(t *testing.T) {
defaultHandler(t)(w, r) defaultHandler(t)(w, r)
})) }))
defer ts.Close() defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false, "", false, nil) provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
require.NoError(t, err) require.NoError(t, err)
_, err = provider.ListRepos(context.Background(), "ssh") _, err = provider.ListRepos(context.Background(), "ssh")
require.Error(t, err) require.Error(t, err)
@@ -583,7 +499,7 @@ func TestListReposCloneProtocol(t *testing.T) {
defaultHandler(t)(w, r) defaultHandler(t)(w, r)
})) }))
defer ts.Close() defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true, "", false, nil) provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true)
require.NoError(t, err) require.NoError(t, err)
repos, err := provider.ListRepos(context.Background(), "https") repos, err := provider.ListRepos(context.Background(), "https")
require.NoError(t, err) require.NoError(t, err)
@@ -605,7 +521,7 @@ func TestListReposUnknownProtocol(t *testing.T) {
defaultHandler(t)(w, r) defaultHandler(t)(w, r)
})) }))
defer ts.Close() defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true, "", false, nil) provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true)
require.NoError(t, err) require.NoError(t, err)
_, errProtocol := provider.ListRepos(context.Background(), "http") _, errProtocol := provider.ListRepos(context.Background(), "http")
require.Error(t, errProtocol) require.Error(t, errProtocol)
@@ -643,7 +559,7 @@ func TestBitbucketServerHasPath(t *testing.T) {
} }
})) }))
defer ts.Close() defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true, "", false, nil) provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true)
require.NoError(t, err) require.NoError(t, err)
repo := &Repository{ repo := &Repository{
Organization: "PROJECT", Organization: "PROJECT",

View File

@@ -128,7 +128,7 @@ func (g *GiteaProvider) ListRepos(ctx context.Context, cloneProtocol string) ([]
func (g *GiteaProvider) RepoHasPath(ctx context.Context, repo *Repository, path string) (bool, error) { func (g *GiteaProvider) RepoHasPath(ctx context.Context, repo *Repository, path string) (bool, error) {
_, resp, err := g.client.GetContents(repo.Organization, repo.Repository, repo.Branch, path) _, resp, err := g.client.GetContents(repo.Organization, repo.Repository, repo.Branch, path)
if resp != nil && resp.StatusCode == http.StatusNotFound { if resp != nil && resp.StatusCode == 404 {
return false, nil return false, nil
} }
if fmt.Sprint(err) == "expect file, got directory" { if fmt.Sprint(err) == "expect file, got directory" {

View File

@@ -15,7 +15,6 @@ import (
) )
func giteaMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) { func giteaMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
t.Helper()
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
switch r.RequestURI { switch r.RequestURI {

View File

@@ -2,11 +2,12 @@ package scm_provider
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
"github.com/google/go-github/v66/github" "github.com/google/go-github/v35/github"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
@@ -35,7 +36,7 @@ func NewGithubProvider(ctx context.Context, organization string, token string, u
client = github.NewClient(httpClient) client = github.NewClient(httpClient)
} else { } else {
var err error var err error
client, err = github.NewClient(httpClient).WithEnterpriseURLs(url, url) client, err = github.NewEnterpriseClient(url, url, httpClient)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -107,7 +108,7 @@ func (g *GithubProvider) RepoHasPath(ctx context.Context, repo *Repository, path
Ref: repo.Branch, Ref: repo.Branch,
}) })
// 404s are not an error here, just a normal false. // 404s are not an error here, just a normal false.
if resp != nil && resp.StatusCode == http.StatusNotFound { if resp != nil && resp.StatusCode == 404 {
return false, nil return false, nil
} }
if err != nil { if err != nil {
@@ -119,11 +120,14 @@ func (g *GithubProvider) RepoHasPath(ctx context.Context, repo *Repository, path
func (g *GithubProvider) listBranches(ctx context.Context, repo *Repository) ([]github.Branch, error) { func (g *GithubProvider) listBranches(ctx context.Context, repo *Repository) ([]github.Branch, error) {
// If we don't specifically want to query for all branches, just use the default branch and call it a day. // If we don't specifically want to query for all branches, just use the default branch and call it a day.
if !g.allBranches { if !g.allBranches {
defaultBranch, resp, err := g.client.Repositories.GetBranch(ctx, repo.Organization, repo.Repository, repo.Branch, 0) defaultBranch, _, err := g.client.Repositories.GetBranch(ctx, repo.Organization, repo.Repository, repo.Branch)
if err != nil { if err != nil {
if resp.StatusCode == http.StatusNotFound { var githubErrorResponse *github.ErrorResponse
// Default branch doesn't exist, so the repo is empty. if errors.As(err, &githubErrorResponse) {
return []github.Branch{}, nil if githubErrorResponse.Response.StatusCode == http.StatusNotFound {
// Default branch doesn't exist, so the repo is empty.
return []github.Branch{}, nil
}
} }
return nil, err return nil, err
} }

View File

@@ -14,7 +14,6 @@ import (
) )
func githubMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) { func githubMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
t.Helper()
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
switch r.RequestURI { switch r.RequestURI {

View File

@@ -24,7 +24,7 @@ type GitlabProvider struct {
var _ SCMProviderService = &GitlabProvider{} var _ SCMProviderService = &GitlabProvider{}
func NewGitlabProvider(ctx context.Context, organization string, token string, url string, allBranches, includeSubgroups, includeSharedProjects, insecure bool, scmRootCAPath, topic string, caCerts []byte) (*GitlabProvider, error) { func NewGitlabProvider(ctx context.Context, organization string, token string, url string, allBranches, includeSubgroups, includeSharedProjects, insecure bool, scmRootCAPath, topic string) (*GitlabProvider, 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("GITLAB_TOKEN") token = os.Getenv("GITLAB_TOKEN")
@@ -32,7 +32,7 @@ func NewGitlabProvider(ctx context.Context, organization string, token string, u
var client *gitlab.Client var client *gitlab.Client
tr := http.DefaultTransport.(*http.Transport).Clone() tr := http.DefaultTransport.(*http.Transport).Clone()
tr.TLSClientConfig = utils.GetTlsConfig(scmRootCAPath, insecure, caCerts) tr.TLSClientConfig = utils.GetTlsConfig(scmRootCAPath, insecure)
retryClient := retryablehttp.NewClient() retryClient := retryablehttp.NewClient()
retryClient.HTTPClient.Transport = tr retryClient.HTTPClient.Transport = tr

View File

@@ -2,8 +2,6 @@ package scm_provider
import ( import (
"context" "context"
"crypto/x509"
"encoding/pem"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@@ -17,7 +15,6 @@ import (
) )
func gitlabMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) { func gitlabMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
t.Helper()
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
switch r.RequestURI { switch r.RequestURI {
@@ -1124,7 +1121,7 @@ func TestGitlabListRepos(t *testing.T) {
})) }))
for _, c := range cases { for _, c := range cases {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
provider, _ := NewGitlabProvider(context.Background(), "test-argocd-proton", "", ts.URL, c.allBranches, c.includeSubgroups, c.includeSharedProjects, c.insecure, "", c.topic, nil) provider, _ := NewGitlabProvider(context.Background(), "test-argocd-proton", "", ts.URL, c.allBranches, c.includeSubgroups, c.includeSharedProjects, c.insecure, "", c.topic)
rawRepos, err := ListRepos(context.Background(), provider, c.filters, c.proto) rawRepos, err := ListRepos(context.Background(), provider, c.filters, c.proto)
if c.hasError { if c.hasError {
require.Error(t, err) require.Error(t, err)
@@ -1163,7 +1160,7 @@ func TestGitlabHasPath(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) {
gitlabMockHandler(t)(w, r) gitlabMockHandler(t)(w, r)
})) }))
host, _ := NewGitlabProvider(context.Background(), "test-argocd-proton", "", ts.URL, false, true, true, false, "", "", nil) host, _ := NewGitlabProvider(context.Background(), "test-argocd-proton", "", ts.URL, false, true, true, false, "", "")
repo := &Repository{ repo := &Repository{
Organization: "test-argocd-proton", Organization: "test-argocd-proton",
Repository: "argocd", Repository: "argocd",
@@ -1209,7 +1206,7 @@ func TestGitlabGetBranches(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) {
gitlabMockHandler(t)(w, r) gitlabMockHandler(t)(w, r)
})) }))
host, _ := NewGitlabProvider(context.Background(), "test-argocd-proton", "", ts.URL, false, true, true, false, "", "", nil) host, _ := NewGitlabProvider(context.Background(), "test-argocd-proton", "", ts.URL, false, true, true, false, "", "")
repo := &Repository{ repo := &Repository{
RepositoryId: 27084533, RepositoryId: 27084533,
@@ -1230,74 +1227,3 @@ func TestGitlabGetBranches(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
}) })
} }
func TestGetBranchesTLS(t *testing.T) {
tests := []struct {
name string
tlsInsecure bool
passCerts bool
requireErr bool
}{
{
name: "TLS Insecure: true, No Certs",
tlsInsecure: true,
passCerts: false,
requireErr: false,
},
{
name: "TLS Insecure: true, With Certs",
tlsInsecure: true,
passCerts: true,
requireErr: false,
},
{
name: "TLS Insecure: false, With Certs",
tlsInsecure: false,
passCerts: true,
requireErr: false,
},
{
name: "TLS Insecure: false, No Certs",
tlsInsecure: false,
passCerts: false,
requireErr: true,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gitlabMockHandler(t)(w, r)
}))
defer ts.Close()
var certs []byte
if test.passCerts == true {
for _, cert := range ts.TLS.Certificates {
for _, c := range cert.Certificate {
parsedCert, err := x509.ParseCertificate(c)
require.NoError(t, err, "Failed to parse certificate")
certs = append(certs, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: parsedCert.Raw,
})...)
}
}
}
host, err := NewGitlabProvider(context.Background(), "test-argocd-proton", "", ts.URL, false, true, true, test.tlsInsecure, "", "", certs)
require.NoError(t, err)
repo := &Repository{
RepositoryId: 27084533,
Branch: "master",
}
_, err = host.GetBranches(context.Background(), repo)
if test.requireErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}

View File

@@ -1,57 +0,0 @@
package status
import (
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func BuildResourceStatus(statusMap map[string]argov1alpha1.ResourceStatus, apps []argov1alpha1.Application) map[string]argov1alpha1.ResourceStatus {
appMap := map[string]argov1alpha1.Application{}
for _, app := range apps {
appCopy := app
appMap[app.Name] = app
gvk := app.GroupVersionKind()
// Create status if it does not exist
status, ok := statusMap[app.Name]
if !ok {
status = argov1alpha1.ResourceStatus{
Group: gvk.Group,
Version: gvk.Version,
Kind: gvk.Kind,
Name: app.Name,
Namespace: app.Namespace,
Status: app.Status.Sync.Status,
Health: &appCopy.Status.Health,
}
}
status.Group = gvk.Group
status.Version = gvk.Version
status.Kind = gvk.Kind
status.Name = app.Name
status.Namespace = app.Namespace
status.Status = app.Status.Sync.Status
status.Health = &appCopy.Status.Health
statusMap[app.Name] = status
}
cleanupDeletedApplicationStatuses(statusMap, appMap)
return statusMap
}
func GetResourceStatusMap(appset *argov1alpha1.ApplicationSet) map[string]argov1alpha1.ResourceStatus {
statusMap := map[string]argov1alpha1.ResourceStatus{}
for _, status := range appset.Status.Resources {
statusMap[status.Name] = status
}
return statusMap
}
func cleanupDeletedApplicationStatuses(statusMap map[string]argov1alpha1.ResourceStatus, apps map[string]argov1alpha1.Application) {
for name := range statusMap {
if _, ok := apps[name]; !ok {
delete(statusMap, name)
}
}
}

View File

@@ -1,63 +0,0 @@
package utils
import (
"context"
"k8s.io/apimachinery/pkg/labels"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
. "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
. "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
)
// Implements AppsetLister interface with controller-runtime client
type AppsetLister struct {
Client ctrlclient.Client
}
func NewAppsetLister(client ctrlclient.Client) ApplicationSetLister {
return &AppsetLister{Client: client}
}
func (l *AppsetLister) List(selector labels.Selector) (ret []*ApplicationSet, err error) {
return clientListAppsets(l.Client, ctrlclient.ListOptions{})
}
// ApplicationSets returns an object that can list and get ApplicationSets.
func (l *AppsetLister) ApplicationSets(namespace string) ApplicationSetNamespaceLister {
return &appsetNamespaceLister{
Client: l.Client,
Namespace: namespace,
}
}
// Implements ApplicationSetNamespaceLister
type appsetNamespaceLister struct {
Client ctrlclient.Client
Namespace string
}
func (n *appsetNamespaceLister) List(selector labels.Selector) (ret []*ApplicationSet, err error) {
return clientListAppsets(n.Client, ctrlclient.ListOptions{Namespace: n.Namespace})
}
func (n *appsetNamespaceLister) Get(name string) (*ApplicationSet, error) {
appset := ApplicationSet{}
err := n.Client.Get(context.TODO(), ctrlclient.ObjectKeyFromObject(&appset), &appset)
return &appset, err
}
func clientListAppsets(client ctrlclient.Client, listOptions ctrlclient.ListOptions) (ret []*ApplicationSet, err error) {
var appsetlist ApplicationSetList
var results []*ApplicationSet
err = client.List(context.TODO(), &appsetlist, &listOptions)
if err == nil {
for _, appset := range appsetlist.Items {
results = append(results, appset.DeepCopy())
}
}
return results, err
}

View File

@@ -51,12 +51,9 @@ const (
// if we used destination name we infer the server url // if we used destination name we infer the server url
// if we used both name and server then we return an invalid spec error // if we used both name and server then we return an invalid spec error
func ValidateDestination(ctx context.Context, dest *appv1.ApplicationDestination, clientset kubernetes.Interface, argoCDNamespace string) error { func ValidateDestination(ctx context.Context, dest *appv1.ApplicationDestination, clientset kubernetes.Interface, argoCDNamespace string) error {
if dest.IsServerInferred() && dest.IsNameInferred() {
return fmt.Errorf("application destination can't have both name and server inferred: %s %s", dest.Name, dest.Server)
}
if dest.Name != "" { if dest.Name != "" {
if dest.Server == "" { if dest.Server == "" {
server, err := getDestinationBy(ctx, dest.Name, clientset, argoCDNamespace, true) server, err := getDestinationServer(ctx, dest.Name, clientset, argoCDNamespace)
if err != nil { if err != nil {
return fmt.Errorf("unable to find destination server: %w", err) return fmt.Errorf("unable to find destination server: %w", err)
} }
@@ -64,25 +61,14 @@ func ValidateDestination(ctx context.Context, dest *appv1.ApplicationDestination
return fmt.Errorf("application references destination cluster %s which does not exist", dest.Name) return fmt.Errorf("application references destination cluster %s which does not exist", dest.Name)
} }
dest.SetInferredServer(server) dest.SetInferredServer(server)
} else if !dest.IsServerInferred() && !dest.IsNameInferred() { } else if !dest.IsServerInferred() {
return fmt.Errorf("application destination can't have both name and server defined: %s %s", dest.Name, dest.Server) return fmt.Errorf("application destination can't have both name and server defined: %s %s", dest.Name, dest.Server)
} }
} else if dest.Server != "" {
if dest.Name == "" {
serverName, err := getDestinationBy(ctx, dest.Server, clientset, argoCDNamespace, false)
if err != nil {
return fmt.Errorf("unable to find destination server: %w", err)
}
if serverName == "" {
return fmt.Errorf("application references destination cluster %s which does not exist", dest.Server)
}
dest.SetInferredName(serverName)
}
} }
return nil return nil
} }
func getDestinationBy(ctx context.Context, cluster string, clientset kubernetes.Interface, argoCDNamespace string, byName bool) (string, error) { func getDestinationServer(ctx context.Context, clusterName string, clientset kubernetes.Interface, argoCDNamespace string) (string, error) {
// settingsMgr := settings.NewSettingsManager(context.TODO(), clientset, namespace) // settingsMgr := settings.NewSettingsManager(context.TODO(), clientset, namespace)
// argoDB := db.NewDB(namespace, settingsMgr, clientset) // argoDB := db.NewDB(namespace, settingsMgr, clientset)
// clusterList, err := argoDB.ListClusters(ctx) // clusterList, err := argoDB.ListClusters(ctx)
@@ -92,17 +78,14 @@ func getDestinationBy(ctx context.Context, cluster string, clientset kubernetes.
} }
var servers []string var servers []string
for _, c := range clusterList.Items { for _, c := range clusterList.Items {
if byName && c.Name == cluster { if c.Name == clusterName {
servers = append(servers, c.Server) servers = append(servers, c.Server)
} }
if !byName && c.Server == cluster {
servers = append(servers, c.Name)
}
} }
if len(servers) > 1 { if len(servers) > 1 {
return "", fmt.Errorf("there are %d clusters with the same name: %v", len(servers), servers) return "", fmt.Errorf("there are %d clusters with the same name: %v", len(servers), servers)
} else if len(servers) == 0 { } else if len(servers) == 0 {
return "", fmt.Errorf("there are no clusters with this name: %s", cluster) return "", fmt.Errorf("there are no clusters with this name: %s", clusterName)
} }
return servers[0], nil return servers[0], nil
} }
@@ -149,12 +132,9 @@ func getLocalCluster(clientset kubernetes.Interface) *appv1.Cluster {
initLocalCluster.Do(func() { initLocalCluster.Do(func() {
info, err := clientset.Discovery().ServerVersion() info, err := clientset.Discovery().ServerVersion()
if err == nil { if err == nil {
// nolint:staticcheck
localCluster.ServerVersion = fmt.Sprintf("%s.%s", info.Major, info.Minor) localCluster.ServerVersion = fmt.Sprintf("%s.%s", info.Major, info.Minor)
// nolint:staticcheck
localCluster.ConnectionState = appv1.ConnectionState{Status: appv1.ConnectionStatusSuccessful} localCluster.ConnectionState = appv1.ConnectionState{Status: appv1.ConnectionStatusSuccessful}
} else { } else {
// nolint:staticcheck
localCluster.ConnectionState = appv1.ConnectionState{ localCluster.ConnectionState = appv1.ConnectionState{
Status: appv1.ConnectionStatusFailed, Status: appv1.ConnectionStatusFailed,
Message: err.Error(), Message: err.Error(),
@@ -163,7 +143,6 @@ func getLocalCluster(clientset kubernetes.Interface) *appv1.Cluster {
}) })
cluster := localCluster.DeepCopy() cluster := localCluster.DeepCopy()
now := metav1.Now() now := metav1.Now()
// nolint:staticcheck
cluster.ConnectionState.ModifiedAt = &now cluster.ConnectionState.ModifiedAt = &now
return cluster return cluster
} }

View File

@@ -30,7 +30,7 @@ func Test_secretToCluster(t *testing.T) {
Data: map[string][]byte{ Data: map[string][]byte{
"name": []byte("test"), "name": []byte("test"),
"server": []byte("http://mycluster"), "server": []byte("http://mycluster"),
"config": []byte("{\"username\":\"foo\", \"disableCompression\":true}"), "config": []byte("{\"username\":\"foo\"}"),
}, },
} }
cluster, err := secretToCluster(secret) cluster, err := secretToCluster(secret)
@@ -39,8 +39,7 @@ func Test_secretToCluster(t *testing.T) {
Name: "test", Name: "test",
Server: "http://mycluster", Server: "http://mycluster",
Config: argoappv1.ClusterConfig{ Config: argoappv1.ClusterConfig{
Username: "foo", Username: "foo",
DisableCompression: true,
}, },
}, *cluster) }, *cluster)
} }
@@ -93,12 +92,7 @@ func TestValidateDestination(t *testing.T) {
Namespace: "default", Namespace: "default",
} }
secret := createClusterSecret("my-secret", "minikube", "https://127.0.0.1:6443") appCond := ValidateDestination(context.Background(), &dest, nil, fakeNamespace)
objects := []runtime.Object{}
objects = append(objects, secret)
kubeclientset := fake.NewSimpleClientset(objects...)
appCond := ValidateDestination(context.Background(), &dest, kubeclientset, fakeNamespace)
require.NoError(t, appCond) require.NoError(t, appCond)
assert.False(t, dest.IsServerInferred()) assert.False(t, dest.IsServerInferred())
}) })

View File

@@ -229,7 +229,7 @@ spec:
require.NoError(t, err) require.NoError(t, err)
yamlExpected, err := yaml.Marshal(tc.expectedApp) yamlExpected, err := yaml.Marshal(tc.expectedApp)
require.NoError(t, err) require.NoError(t, err)
assert.YAMLEq(t, string(yamlExpected), string(yamlFound)) assert.Equal(t, string(yamlExpected), string(yamlFound))
}) })
} }
} }

View File

@@ -1,63 +0,0 @@
package utils
import (
"context"
"fmt"
"github.com/argoproj/argo-cd/v2/common"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
var ErrDisallowedSecretAccess = fmt.Errorf("secret must have label %q=%q", common.LabelKeySecretType, common.LabelValueSecretTypeSCMCreds)
// getSecretRef gets the value of the key for the specified Secret resource.
func GetSecretRef(ctx context.Context, k8sClient client.Client, ref *argoprojiov1alpha1.SecretRef, namespace string, tokenRefStrictMode bool) (string, error) {
if ref == nil {
return "", nil
}
secret := &corev1.Secret{}
err := k8sClient.Get(
ctx,
client.ObjectKey{
Name: ref.SecretName,
Namespace: namespace,
},
secret)
if err != nil {
return "", fmt.Errorf("error fetching secret %s/%s: %w", namespace, ref.SecretName, err)
}
if tokenRefStrictMode && secret.GetLabels()[common.LabelKeySecretType] != common.LabelValueSecretTypeSCMCreds {
return "", fmt.Errorf("secret %s/%s is not a valid SCM creds secret: %w", namespace, ref.SecretName, ErrDisallowedSecretAccess)
}
tokenBytes, ok := secret.Data[ref.Key]
if !ok {
return "", fmt.Errorf("key %q in secret %s/%s not found", ref.Key, namespace, ref.SecretName)
}
return string(tokenBytes), nil
}
func GetConfigMapData(ctx context.Context, k8sClient client.Client, ref *argoprojiov1alpha1.ConfigMapKeyRef, namespace string) ([]byte, error) {
if ref == nil {
return nil, nil
}
configMap := &corev1.ConfigMap{}
err := k8sClient.Get(ctx, client.ObjectKey{Name: ref.ConfigMapName, Namespace: namespace}, configMap)
if err != nil {
return nil, err
}
data, ok := configMap.Data[ref.Key]
if !ok {
return nil, fmt.Errorf("key %s not found in ConfigMap %s", ref.Key, configMap.Name)
}
return []byte(data), nil
}

View File

@@ -1,146 +0,0 @@
package utils
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func TestGetSecretRef(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test-secret", Namespace: "test"},
Data: map[string][]byte{
"my-token": []byte("secret"),
},
}
client := fake.NewClientBuilder().WithObjects(secret).Build()
ctx := context.Background()
cases := []struct {
name, namespace, token string
ref *argoprojiov1alpha1.SecretRef
hasError bool
}{
{
name: "valid ref",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
namespace: "test",
token: "secret",
hasError: false,
},
{
name: "nil ref",
ref: nil,
namespace: "test",
token: "",
hasError: false,
},
{
name: "wrong name",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "other", Key: "my-token"},
namespace: "test",
token: "",
hasError: true,
},
{
name: "wrong key",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "other-token"},
namespace: "test",
token: "",
hasError: true,
},
{
name: "wrong namespace",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
namespace: "other",
token: "",
hasError: true,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
token, err := GetSecretRef(ctx, client, c.ref, c.namespace, false)
if c.hasError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
assert.Equal(t, c.token, token)
})
}
}
func TestGetConfigMapData(t *testing.T) {
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: "test-configmap", Namespace: "test"},
Data: map[string]string{
"my-data": "configmap-data",
},
}
client := fake.NewClientBuilder().WithObjects(configMap).Build()
ctx := context.Background()
cases := []struct {
name, namespace, data string
ref *argoprojiov1alpha1.ConfigMapKeyRef
hasError bool
}{
{
name: "valid ref",
ref: &argoprojiov1alpha1.ConfigMapKeyRef{ConfigMapName: "test-configmap", Key: "my-data"},
namespace: "test",
data: "configmap-data",
hasError: false,
},
{
name: "nil ref",
ref: nil,
namespace: "test",
data: "",
hasError: false,
},
{
name: "wrong name",
ref: &argoprojiov1alpha1.ConfigMapKeyRef{ConfigMapName: "other", Key: "my-data"},
namespace: "test",
data: "",
hasError: true,
},
{
name: "wrong key",
ref: &argoprojiov1alpha1.ConfigMapKeyRef{ConfigMapName: "test-configmap", Key: "other-data"},
namespace: "test",
data: "",
hasError: true,
},
{
name: "wrong namespace",
ref: &argoprojiov1alpha1.ConfigMapKeyRef{ConfigMapName: "test-configmap", Key: "my-data"},
namespace: "other",
data: "",
hasError: true,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
data, err := GetConfigMapData(ctx, client, c.ref, c.namespace)
if c.hasError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
if !c.hasError {
assert.Equal(t, c.data, string(data))
}
})
}
}

View File

@@ -8,7 +8,10 @@ func ConvertToMapStringString(mapStringInterface map[string]interface{}) map[str
mapStringString := make(map[string]string, len(mapStringInterface)) mapStringString := make(map[string]string, len(mapStringInterface))
for key, value := range mapStringInterface { for key, value := range mapStringInterface {
mapStringString[key] = fmt.Sprintf("%v", value) strKey := fmt.Sprintf("%v", key)
strValue := fmt.Sprintf("%v", value)
mapStringString[strKey] = strValue
} }
return mapStringString return mapStringString
} }

View File

@@ -1,86 +0,0 @@
// Code generated by mockery v2.43.2. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
// Renderer is an autogenerated mock type for the Renderer type
type Renderer struct {
mock.Mock
}
// RenderTemplateParams provides a mock function with given fields: tmpl, syncPolicy, params, useGoTemplate, goTemplateOptions
func (_m *Renderer) RenderTemplateParams(tmpl *v1alpha1.Application, syncPolicy *v1alpha1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (*v1alpha1.Application, error) {
ret := _m.Called(tmpl, syncPolicy, params, useGoTemplate, goTemplateOptions)
if len(ret) == 0 {
panic("no return value specified for RenderTemplateParams")
}
var r0 *v1alpha1.Application
var r1 error
if rf, ok := ret.Get(0).(func(*v1alpha1.Application, *v1alpha1.ApplicationSetSyncPolicy, map[string]interface{}, bool, []string) (*v1alpha1.Application, error)); ok {
return rf(tmpl, syncPolicy, params, useGoTemplate, goTemplateOptions)
}
if rf, ok := ret.Get(0).(func(*v1alpha1.Application, *v1alpha1.ApplicationSetSyncPolicy, map[string]interface{}, bool, []string) *v1alpha1.Application); ok {
r0 = rf(tmpl, syncPolicy, params, useGoTemplate, goTemplateOptions)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.Application)
}
}
if rf, ok := ret.Get(1).(func(*v1alpha1.Application, *v1alpha1.ApplicationSetSyncPolicy, map[string]interface{}, bool, []string) error); ok {
r1 = rf(tmpl, syncPolicy, params, useGoTemplate, goTemplateOptions)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Replace provides a mock function with given fields: tmpl, replaceMap, useGoTemplate, goTemplateOptions
func (_m *Renderer) Replace(tmpl string, replaceMap map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (string, error) {
ret := _m.Called(tmpl, replaceMap, useGoTemplate, goTemplateOptions)
if len(ret) == 0 {
panic("no return value specified for Replace")
}
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func(string, map[string]interface{}, bool, []string) (string, error)); ok {
return rf(tmpl, replaceMap, useGoTemplate, goTemplateOptions)
}
if rf, ok := ret.Get(0).(func(string, map[string]interface{}, bool, []string) string); ok {
r0 = rf(tmpl, replaceMap, useGoTemplate, goTemplateOptions)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(string, map[string]interface{}, bool, []string) error); ok {
r1 = rf(tmpl, replaceMap, useGoTemplate, goTemplateOptions)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewRenderer creates a new instance of Renderer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewRenderer(t interface {
mock.TestingT
Cleanup(func())
}) *Renderer {
mock := &Renderer{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -23,7 +23,6 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
argoappsv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoappsv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/glob"
) )
var sprigFuncMap = sprig.GenericFuncMap() // a singleton for better performance var sprigFuncMap = sprig.GenericFuncMap() // a singleton for better performance
@@ -47,10 +46,6 @@ type Renderer interface {
type Render struct{} type Render struct{}
func IsNamespaceAllowed(namespaces []string, namespace string) bool {
return glob.MatchStringInList(namespaces, namespace, glob.REGEXP)
}
func copyValueIntoUnexported(destination, value reflect.Value) { func copyValueIntoUnexported(destination, value reflect.Value) {
reflect.NewAt(destination.Type(), unsafe.Pointer(destination.UnsafeAddr())). reflect.NewAt(destination.Type(), unsafe.Pointer(destination.UnsafeAddr())).
Elem(). Elem().
@@ -273,7 +268,7 @@ func (r *Render) RenderTemplateParams(tmpl *argoappsv1.Application, syncPolicy *
// b) there IS a syncPolicy, but preserveResourcesOnDeletion is set to false // b) there IS a syncPolicy, but preserveResourcesOnDeletion is set to false
// See TestRenderTemplateParamsFinalizers in util_test.go for test-based definition of behaviour // See TestRenderTemplateParamsFinalizers in util_test.go for test-based definition of behaviour
if (syncPolicy == nil || !syncPolicy.PreserveResourcesOnDeletion) && if (syncPolicy == nil || !syncPolicy.PreserveResourcesOnDeletion) &&
len(replacedTmpl.ObjectMeta.Finalizers) == 0 { (replacedTmpl.ObjectMeta.Finalizers == nil || len(replacedTmpl.ObjectMeta.Finalizers) == 0) {
replacedTmpl.ObjectMeta.Finalizers = []string{"resources-finalizer.argocd.argoproj.io"} replacedTmpl.ObjectMeta.Finalizers = []string{"resources-finalizer.argocd.argoproj.io"}
} }
@@ -488,7 +483,7 @@ func SlugifyName(args ...interface{}) string {
return urlSlug return urlSlug
} }
func getTlsConfigWithCACert(scmRootCAPath string, caCerts []byte) *tls.Config { func getTlsConfigWithCACert(scmRootCAPath string) *tls.Config {
tlsConfig := &tls.Config{} tlsConfig := &tls.Config{}
if scmRootCAPath != "" { if scmRootCAPath != "" {
@@ -502,12 +497,8 @@ func getTlsConfigWithCACert(scmRootCAPath string, caCerts []byte) *tls.Config {
log.Errorf("error reading certificate from file '%s', proceeding without custom rootCA : %s", scmRootCAPath, err) log.Errorf("error reading certificate from file '%s', proceeding without custom rootCA : %s", scmRootCAPath, err)
return tlsConfig return tlsConfig
} }
caCerts = append(caCerts, rootCA...)
}
if len(caCerts) > 0 {
certPool := x509.NewCertPool() certPool := x509.NewCertPool()
ok := certPool.AppendCertsFromPEM(caCerts) ok := certPool.AppendCertsFromPEM([]byte(rootCA))
if !ok { if !ok {
log.Errorf("failed to append certificates from PEM: proceeding without custom rootCA") log.Errorf("failed to append certificates from PEM: proceeding without custom rootCA")
} else { } else {
@@ -517,8 +508,8 @@ func getTlsConfigWithCACert(scmRootCAPath string, caCerts []byte) *tls.Config {
return tlsConfig return tlsConfig
} }
func GetTlsConfig(scmRootCAPath string, insecure bool, caCerts []byte) *tls.Config { func GetTlsConfig(scmRootCAPath string, insecure bool) *tls.Config {
tlsConfig := getTlsConfigWithCACert(scmRootCAPath, caCerts) tlsConfig := getTlsConfigWithCACert(scmRootCAPath)
if insecure { if insecure {
tlsConfig.InsecureSkipVerify = true tlsConfig.InsecureSkipVerify = true

View File

@@ -1260,8 +1260,11 @@ func TestSlugify(t *testing.T) {
} }
func TestGetTLSConfig(t *testing.T) { func TestGetTLSConfig(t *testing.T) {
// certParsed, err := tls.X509KeyPair(test.Cert, test.PrivateKey)
// require.NoError(t, err)
temppath := t.TempDir() temppath := t.TempDir()
certFromFile := ` cert := `
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIFvTCCA6WgAwIBAgIUGrTmW3qc39zqnE08e3qNDhUkeWswDQYJKoZIhvcNAQEL MIIFvTCCA6WgAwIBAgIUGrTmW3qc39zqnE08e3qNDhUkeWswDQYJKoZIhvcNAQEL
BQAwbjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAklMMRAwDgYDVQQHDAdDaGljYWdv BQAwbjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAklMMRAwDgYDVQQHDAdDaGljYWdv
@@ -1295,96 +1298,50 @@ NoB2rjufaB0GQi1azdboMvdGSOxhSCAR8otWT5yDrywCqVnEvjw0oxKmuRduNe2/
r2AaraPFgrprnxUibP4L7jxdr+iiw5bWN9/B81PodrS7n5TNtnfnpZD6X6rThqOP r2AaraPFgrprnxUibP4L7jxdr+iiw5bWN9/B81PodrS7n5TNtnfnpZD6X6rThqOP
xO7Tr5lAo74vNUkF2EHNaI28/RGnJPm2TIxZqy4rNH6L xO7Tr5lAo74vNUkF2EHNaI28/RGnJPm2TIxZqy4rNH6L
-----END CERTIFICATE----- -----END CERTIFICATE-----
`
certFromCM := `
-----BEGIN CERTIFICATE-----
MIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEA6Gba5tHV1dAKouAaXO3/ebDUU4rvwCUg/CNaJ2PT5xLD4N1Vcb8r
bFSW2HXKq+MPfVdwIKR/1DczEoAGf/JWQTW7EgzlXrCd3rlajEX2D73faWJekD0U
aUgz5vtrTXZ90BQL7WvRICd7FlEZ6FPOcPlumiyNmzUqtwGhO+9ad1W5BqJaRI6P
YfouNkwR6Na4TzSj5BrqUfP0FwDizKSJ0XXmh8g8G9mtwxOSN3Ru1QFc61Xyeluk
POGKBV/q6RBNklTNe0gI8usUMlYyoC7ytppNMW7X2vodAelSu25jgx2anj9fDVZu
h7AXF5+4nJS4AAt0n1lNY7nGSsdZas8PbQIDAQABo4GIMIGFMA4GA1UdDwEB/wQE
AwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBStsdjh3/JCXXYlQryOrL4Sh7BW5TAuBgNVHREEJzAlggtleGFtcGxlLmNv
bYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAxWGI
5NhpF3nwwy/4yB4i/CwwSpLrWUa70NyhvprUBC50PxiXav1TeDzwzLx/o5HyNwsv
cxv3HdkLW59i/0SlJSrNnWdfZ19oTcS+6PtLoVyISgtyN6DpkKpdG1cOkW3Cy2P2
+tK/tKHRP1Y/Ra0RiDpOAmqn0gCOFGz8+lqDIor/T7MTpibL3IxqWfPrvfVRHL3B
grw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK
5d+NTDREkSnUbie4GeutujmX3Dsx88UiV6UY/4lHJa6I5leHUNOHahRbpbWeOfs/
WkBKOclmOV2xlTVuPw==
-----END CERTIFICATE-----
` `
rootCAPath := path.Join(temppath, "foo.example.com") rootCAPath := path.Join(temppath, "foo.example.com")
err := os.WriteFile(rootCAPath, []byte(certFromFile), 0o666) err := os.WriteFile(rootCAPath, []byte(cert), 0o666)
if err != nil { if err != nil {
panic(err) panic(err)
} }
certPool := x509.NewCertPool()
ok := certPool.AppendCertsFromPEM([]byte(cert))
assert.True(t, ok)
testCases := []struct { testCases := []struct {
name string name string
scmRootCAPath string scmRootCAPath string
insecure bool insecure bool
caCerts []byte
validateCertInTlsConfig bool validateCertInTlsConfig bool
}{ }{
{ {
name: "Insecure mode configured, SCM Root CA Path not set", name: "Insecure mode configured, SCM Root CA Path not set",
scmRootCAPath: "", scmRootCAPath: "",
insecure: true, insecure: true,
caCerts: nil,
validateCertInTlsConfig: false, validateCertInTlsConfig: false,
}, },
{ {
name: "SCM Root CA Path set, Insecure mode set to false", name: "SCM Root CA Path set, Insecure mode set to false",
scmRootCAPath: rootCAPath, scmRootCAPath: rootCAPath,
insecure: false, insecure: false,
caCerts: nil,
validateCertInTlsConfig: true, validateCertInTlsConfig: true,
}, },
{ {
name: "SCM Root CA Path set, Insecure mode set to true", name: "SCM Root CA Path set, Insecure mode set to true",
scmRootCAPath: rootCAPath, scmRootCAPath: rootCAPath,
insecure: true, insecure: true,
caCerts: nil,
validateCertInTlsConfig: true,
},
{
name: "Cert passed, Insecure mode set to false",
scmRootCAPath: "",
insecure: false,
caCerts: []byte(certFromCM),
validateCertInTlsConfig: true,
},
{
name: "SCM Root CA Path set, cert passed, Insecure mode set to false",
scmRootCAPath: rootCAPath,
insecure: false,
caCerts: []byte(certFromCM),
validateCertInTlsConfig: true, validateCertInTlsConfig: true,
}, },
} }
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
certPool := x509.NewCertPool() tlsConfig := GetTlsConfig(testCase.scmRootCAPath, testCase.insecure)
tlsConfig := GetTlsConfig(testCase.scmRootCAPath, testCase.insecure, testCase.caCerts)
assert.Equal(t, testCase.insecure, tlsConfig.InsecureSkipVerify) assert.Equal(t, testCase.insecure, tlsConfig.InsecureSkipVerify)
if testCase.caCerts != nil {
ok := certPool.AppendCertsFromPEM([]byte(certFromCM))
assert.True(t, ok)
}
if testCase.scmRootCAPath != "" {
ok := certPool.AppendCertsFromPEM([]byte(certFromFile))
assert.True(t, ok)
}
assert.NotNil(t, tlsConfig)
if testCase.validateCertInTlsConfig { if testCase.validateCertInTlsConfig {
assert.NotNil(t, tlsConfig)
assert.True(t, tlsConfig.RootCAs.Equal(certPool)) assert.True(t, tlsConfig.RootCAs.Equal(certPool))
} }
}) })

View File

@@ -1,186 +0,0 @@
{
"ref": "refs/heads/env/dev",
"before": "d5c1ffa8e294bc18c639bfb4e0df499251034414",
"after": "63738bb582c8b540af7bcfc18f87c575c3ed66e0",
"created": false,
"deleted": false,
"forced": true,
"base_ref": null,
"compare": "https://github.com/org/repo/compare/d5c1ffa8e294...63738bb582c8",
"commits": [
{
"id": "63738bb582c8b540af7bcfc18f87c575c3ed66e0",
"tree_id": "64897da445207e409ad05af93b1f349ad0a4ee19",
"distinct": true,
"message": "Add staging-argocd-demo environment",
"timestamp": "2018-05-04T15:40:02-07:00",
"url": "https://github.com/org/repo/commit/63738bb582c8b540af7bcfc18f87c575c3ed66e0",
"author": {
"name": "Jesse Suen",
"email": "Jesse_Suen@example.com",
"username": "org"
},
"committer": {
"name": "Jesse Suen",
"email": "Jesse_Suen@example.com",
"username": "org"
},
"added": [
"ksapps/test-app/environments/staging-argocd-demo/main.jsonnet",
"ksapps/test-app/environments/staging-argocd-demo/params.libsonnet"
],
"removed": [
],
"modified": [
"ksapps/test-app/app.yaml"
]
}
],
"head_commit": {
"id": "63738bb582c8b540af7bcfc18f87c575c3ed66e0",
"tree_id": "64897da445207e409ad05af93b1f349ad0a4ee19",
"distinct": true,
"message": "Add staging-argocd-demo environment",
"timestamp": "2018-05-04T15:40:02-07:00",
"url": "https://github.com/org/repo/commit/63738bb582c8b540af7bcfc18f87c575c3ed66e0",
"author": {
"name": "Jesse Suen",
"email": "Jesse_Suen@example.com",
"username": "org"
},
"committer": {
"name": "Jesse Suen",
"email": "Jesse_Suen@example.com",
"username": "org"
},
"added": [
"ksapps/test-app/environments/staging-argocd-demo/main.jsonnet",
"ksapps/test-app/environments/staging-argocd-demo/params.libsonnet"
],
"removed": [
],
"modified": [
"ksapps/test-app/app.yaml"
]
},
"repository": {
"id": 123060978,
"name": "repo",
"full_name": "org/repo",
"owner": {
"name": "org",
"email": "org@users.noreply.github.com",
"login": "org",
"id": 12677113,
"avatar_url": "https://avatars0.githubusercontent.com/u/12677113?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/org",
"html_url": "https://github.com/org",
"followers_url": "https://api.github.com/users/org/followers",
"following_url": "https://api.github.com/users/org/following{/other_user}",
"gists_url": "https://api.github.com/users/org/gists{/gist_id}",
"starred_url": "https://api.github.com/users/org/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/org/subscriptions",
"organizations_url": "https://api.github.com/users/org/orgs",
"repos_url": "https://api.github.com/users/org/repos",
"events_url": "https://api.github.com/users/org/events{/privacy}",
"received_events_url": "https://api.github.com/users/org/received_events",
"type": "User",
"site_admin": false
},
"private": false,
"html_url": "https://github.com/org/repo",
"description": "Test Repository",
"fork": false,
"url": "https://github.com/org/repo",
"forks_url": "https://api.github.com/repos/org/repo/forks",
"keys_url": "https://api.github.com/repos/org/repo/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/org/repo/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/org/repo/teams",
"hooks_url": "https://api.github.com/repos/org/repo/hooks",
"issue_events_url": "https://api.github.com/repos/org/repo/issues/events{/number}",
"events_url": "https://api.github.com/repos/org/repo/events",
"assignees_url": "https://api.github.com/repos/org/repo/assignees{/user}",
"branches_url": "https://api.github.com/repos/org/repo/branches{/branch}",
"tags_url": "https://api.github.com/repos/org/repo/tags",
"blobs_url": "https://api.github.com/repos/org/repo/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/org/repo/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/org/repo/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/org/repo/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/org/repo/statuses/{sha}",
"languages_url": "https://api.github.com/repos/org/repo/languages",
"stargazers_url": "https://api.github.com/repos/org/repo/stargazers",
"contributors_url": "https://api.github.com/repos/org/repo/contributors",
"subscribers_url": "https://api.github.com/repos/org/repo/subscribers",
"subscription_url": "https://api.github.com/repos/org/repo/subscription",
"commits_url": "https://api.github.com/repos/org/repo/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/org/repo/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/org/repo/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/org/repo/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/org/repo/contents/{+path}",
"compare_url": "https://api.github.com/repos/org/repo/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/org/repo/merges",
"archive_url": "https://api.github.com/repos/org/repo/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/org/repo/downloads",
"issues_url": "https://api.github.com/repos/org/repo/issues{/number}",
"pulls_url": "https://api.github.com/repos/org/repo/pulls{/number}",
"milestones_url": "https://api.github.com/repos/org/repo/milestones{/number}",
"notifications_url": "https://api.github.com/repos/org/repo/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/org/repo/labels{/name}",
"releases_url": "https://api.github.com/repos/org/repo/releases{/id}",
"deployments_url": "https://api.github.com/repos/org/repo/deployments",
"created_at": 1519698615,
"updated_at": "2018-05-04T22:37:55Z",
"pushed_at": 1525473610,
"git_url": "git://github.com/org/repo.git",
"ssh_url": "git@github.com:org/repo.git",
"clone_url": "https://github.com/org/repo.git",
"svn_url": "https://github.com/org/repo",
"homepage": null,
"size": 538,
"stargazers_count": 0,
"watchers_count": 0,
"language": null,
"has_issues": true,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"forks_count": 1,
"mirror_url": null,
"archived": false,
"open_issues_count": 0,
"license": null,
"forks": 1,
"open_issues": 0,
"watchers": 0,
"default_branch": "master",
"stargazers": 0,
"master_branch": "master"
},
"pusher": {
"name": "org",
"email": "org@users.noreply.github.com"
},
"sender": {
"login": "org",
"id": 12677113,
"avatar_url": "https://avatars0.githubusercontent.com/u/12677113?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/org",
"html_url": "https://github.com/org",
"followers_url": "https://api.github.com/users/org/followers",
"following_url": "https://api.github.com/users/org/following{/other_user}",
"gists_url": "https://api.github.com/users/org/gists{/gist_id}",
"starred_url": "https://api.github.com/users/org/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/org/subscriptions",
"organizations_url": "https://api.github.com/users/org/orgs",
"repos_url": "https://api.github.com/users/org/repos",
"events_url": "https://api.github.com/users/org/events{/privacy}",
"received_events_url": "https://api.github.com/users/org/received_events",
"type": "User",
"site_admin": false
}
}

View File

@@ -2,6 +2,7 @@ package webhook
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"html" "html"
"net/http" "net/http"
@@ -9,7 +10,6 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"sync"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/util/retry" "k8s.io/client-go/util/retry"
@@ -19,7 +19,6 @@ import (
"github.com/argoproj/argo-cd/v2/common" "github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
argosettings "github.com/argoproj/argo-cd/v2/util/settings" argosettings "github.com/argoproj/argo-cd/v2/util/settings"
"github.com/argoproj/argo-cd/v2/util/webhook"
"github.com/go-playground/webhooks/v6/azuredevops" "github.com/go-playground/webhooks/v6/azuredevops"
"github.com/go-playground/webhooks/v6/github" "github.com/go-playground/webhooks/v6/github"
@@ -27,17 +26,16 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
const payloadQueueSize = 50000 var errBasicAuthVerificationFailed = errors.New("basic auth verification failed")
type WebhookHandler struct { type WebhookHandler struct {
sync.WaitGroup // for testing namespace string
namespace string github *github.Webhook
github *github.Webhook gitlab *gitlab.Webhook
gitlab *gitlab.Webhook azuredevops *azuredevops.Webhook
azuredevops *azuredevops.Webhook azuredevopsAuthHandler func(r *http.Request) error
client client.Client client client.Client
generators map[string]generators.Generator generators map[string]generators.Generator
queue chan interface{}
} }
type gitGeneratorInfo struct { type gitGeneratorInfo struct {
@@ -68,7 +66,7 @@ type prGeneratorGitlabInfo struct {
APIHostname string APIHostname string
} }
func NewWebhookHandler(namespace string, webhookParallelism int, argocdSettingsMgr *argosettings.SettingsManager, client client.Client, generators map[string]generators.Generator) (*WebhookHandler, error) { func NewWebhookHandler(namespace string, argocdSettingsMgr *argosettings.SettingsManager, client client.Client, generators map[string]generators.Generator) (*WebhookHandler, error) {
// register the webhook secrets stored under "argocd-secret" for verifying incoming payloads // register the webhook secrets stored under "argocd-secret" for verifying incoming payloads
argocdSettings, err := argocdSettingsMgr.GetSettings() argocdSettings, err := argocdSettingsMgr.GetSettings()
if err != nil { if err != nil {
@@ -82,40 +80,29 @@ func NewWebhookHandler(namespace string, webhookParallelism int, argocdSettingsM
if err != nil { if err != nil {
return nil, fmt.Errorf("Unable to init GitLab webhook: %w", err) return nil, fmt.Errorf("Unable to init GitLab webhook: %w", err)
} }
azuredevopsHandler, err := azuredevops.New(azuredevops.Options.BasicAuth(argocdSettings.WebhookAzureDevOpsUsername, argocdSettings.WebhookAzureDevOpsPassword)) azuredevopsHandler, err := azuredevops.New()
if err != nil { if err != nil {
return nil, fmt.Errorf("Unable to init Azure DevOps webhook: %w", err) return nil, fmt.Errorf("Unable to init Azure DevOps webhook: %w", err)
} }
azuredevopsAuthHandler := func(r *http.Request) error {
webhookHandler := &WebhookHandler{ if argocdSettings.WebhookAzureDevOpsUsername != "" && argocdSettings.WebhookAzureDevOpsPassword != "" {
namespace: namespace, username, password, ok := r.BasicAuth()
github: githubHandler, if !ok || username != argocdSettings.WebhookAzureDevOpsUsername || password != argocdSettings.WebhookAzureDevOpsPassword {
gitlab: gitlabHandler, return errBasicAuthVerificationFailed
azuredevops: azuredevopsHandler,
client: client,
generators: generators,
queue: make(chan interface{}, payloadQueueSize),
}
webhookHandler.startWorkerPool(webhookParallelism)
return webhookHandler, nil
}
func (h *WebhookHandler) startWorkerPool(webhookParallelism int) {
for i := 0; i < webhookParallelism; i++ {
h.Add(1)
go func() {
defer h.Done()
for {
payload, ok := <-h.queue
if !ok {
return
}
h.HandleEvent(payload)
} }
}() }
return nil
} }
return &WebhookHandler{
namespace: namespace,
github: githubHandler,
gitlab: gitlabHandler,
azuredevops: azuredevopsHandler,
azuredevopsAuthHandler: azuredevopsAuthHandler,
client: client,
generators: generators,
}, nil
} }
func (h *WebhookHandler) HandleEvent(payload interface{}) { func (h *WebhookHandler) HandleEvent(payload interface{}) {
@@ -166,7 +153,13 @@ func (h *WebhookHandler) Handler(w http.ResponseWriter, r *http.Request) {
case r.Header.Get("X-Gitlab-Event") != "": case r.Header.Get("X-Gitlab-Event") != "":
payload, err = h.gitlab.Parse(r, gitlab.PushEvents, gitlab.TagEvents, gitlab.MergeRequestEvents) payload, err = h.gitlab.Parse(r, gitlab.PushEvents, gitlab.TagEvents, gitlab.MergeRequestEvents)
case r.Header.Get("X-Vss-Activityid") != "": case r.Header.Get("X-Vss-Activityid") != "":
payload, err = h.azuredevops.Parse(r, azuredevops.GitPushEventType, azuredevops.GitPullRequestCreatedEventType, azuredevops.GitPullRequestUpdatedEventType, azuredevops.GitPullRequestMergedEventType) if err = h.azuredevopsAuthHandler(r); err != nil {
if errors.Is(err, errBasicAuthVerificationFailed) {
log.WithField(common.SecurityField, common.SecurityHigh).Infof("Azure DevOps webhook basic auth verification failed")
}
} else {
payload, err = h.azuredevops.Parse(r, azuredevops.GitPushEventType, azuredevops.GitPullRequestCreatedEventType, azuredevops.GitPullRequestUpdatedEventType, azuredevops.GitPullRequestMergedEventType)
}
default: default:
log.Debug("Ignoring unknown webhook event") log.Debug("Ignoring unknown webhook event")
http.Error(w, "Unknown webhook event", http.StatusBadRequest) http.Error(w, "Unknown webhook event", http.StatusBadRequest)
@@ -183,12 +176,12 @@ func (h *WebhookHandler) Handler(w http.ResponseWriter, r *http.Request) {
return return
} }
select { h.HandleEvent(payload)
case h.queue <- payload: }
default:
log.Info("Queue is full, discarding webhook payload") func parseRevision(ref string) string {
http.Error(w, "Queue is full, discarding webhook payload", http.StatusServiceUnavailable) refParts := strings.SplitN(ref, "/", 3)
} return refParts[len(refParts)-1]
} }
func getGitGeneratorInfo(payload interface{}) *gitGeneratorInfo { func getGitGeneratorInfo(payload interface{}) *gitGeneratorInfo {
@@ -200,16 +193,16 @@ func getGitGeneratorInfo(payload interface{}) *gitGeneratorInfo {
switch payload := payload.(type) { switch payload := payload.(type) {
case github.PushPayload: case github.PushPayload:
webURL = payload.Repository.HTMLURL webURL = payload.Repository.HTMLURL
revision = webhook.ParseRevision(payload.Ref) revision = parseRevision(payload.Ref)
touchedHead = payload.Repository.DefaultBranch == revision touchedHead = payload.Repository.DefaultBranch == revision
case gitlab.PushEventPayload: case gitlab.PushEventPayload:
webURL = payload.Project.WebURL webURL = payload.Project.WebURL
revision = webhook.ParseRevision(payload.Ref) revision = parseRevision(payload.Ref)
touchedHead = payload.Project.DefaultBranch == revision touchedHead = payload.Project.DefaultBranch == revision
case azuredevops.GitPushEvent: case azuredevops.GitPushEvent:
// See: https://learn.microsoft.com/en-us/azure/devops/service-hooks/events?view=azure-devops#git.push // See: https://learn.microsoft.com/en-us/azure/devops/service-hooks/events?view=azure-devops#git.push
webURL = payload.Resource.Repository.RemoteURL webURL = payload.Resource.Repository.RemoteURL
revision = webhook.ParseRevision(payload.Resource.RefUpdates[0].Name) revision = parseRevision(payload.Resource.RefUpdates[0].Name)
touchedHead = payload.Resource.RefUpdates[0].Name == payload.Resource.Repository.DefaultBranch touchedHead = payload.Resource.RefUpdates[0].Name == payload.Resource.Repository.DefaultBranch
// unfortunately, Azure DevOps doesn't provide a list of changed files // unfortunately, Azure DevOps doesn't provide a list of changed files
default: default:
@@ -222,7 +215,7 @@ func getGitGeneratorInfo(payload interface{}) *gitGeneratorInfo {
log.Errorf("Failed to parse repoURL '%s'", webURL) log.Errorf("Failed to parse repoURL '%s'", webURL)
return nil return nil
} }
regexpStr := `(?i)(http://|https://|\w+@|ssh://(\w+@)?)` + urlObj.Hostname() + "(:[0-9]+|)[:/]" + urlObj.Path[1:] + "(\\.git)?$" regexpStr := `(?i)(http://|https://|\w+@|ssh://(\w+@)?)` + urlObj.Hostname() + "(:[0-9]+|)[:/]" + urlObj.Path[1:] + "(\\.git)?"
repoRegexp, err := regexp.Compile(regexpStr) repoRegexp, err := regexp.Compile(regexpStr)
if err != nil { if err != nil {
log.Errorf("Failed to compile regexp for repoURL '%s'", webURL) log.Errorf("Failed to compile regexp for repoURL '%s'", webURL)
@@ -369,12 +362,12 @@ func shouldRefreshPluginGenerator(gen *v1alpha1.PluginGenerator) bool {
} }
func genRevisionHasChanged(gen *v1alpha1.GitGenerator, revision string, touchedHead bool) bool { func genRevisionHasChanged(gen *v1alpha1.GitGenerator, revision string, touchedHead bool) bool {
targetRev := webhook.ParseRevision(gen.Revision) targetRev := parseRevision(gen.Revision)
if targetRev == "HEAD" || targetRev == "" { // revision is head if targetRev == "HEAD" || targetRev == "" { // revision is head
return touchedHead return touchedHead
} }
return targetRev == revision || gen.Revision == revision return targetRev == revision
} }
func gitGeneratorUsesURL(gen *v1alpha1.GitGenerator, webURL string, repoRegexp *regexp.Regexp) bool { func gitGeneratorUsesURL(gen *v1alpha1.GitGenerator, webURL string, repoRegexp *regexp.Regexp) bool {

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