mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-03-20 23:38:47 +01:00
Compare commits
1 Commits
commit-ser
...
security-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7a0dff47e |
@@ -8,7 +8,6 @@ ignore:
|
|||||||
- "pkg/client/.*"
|
- "pkg/client/.*"
|
||||||
- "vendor/.*"
|
- "vendor/.*"
|
||||||
- "test/.*"
|
- "test/.*"
|
||||||
- "**/mocks/*"
|
|
||||||
coverage:
|
coverage:
|
||||||
status:
|
status:
|
||||||
# we've found this not to be useful
|
# we've found this not to be useful
|
||||||
|
|||||||
@@ -11,19 +11,3 @@ cmd/**/debug
|
|||||||
debug.test
|
debug.test
|
||||||
coverage.out
|
coverage.out
|
||||||
ui/node_modules/
|
ui/node_modules/
|
||||||
test-results/
|
|
||||||
test/
|
|
||||||
manifests/
|
|
||||||
hack/
|
|
||||||
docs/
|
|
||||||
examples/
|
|
||||||
.github/
|
|
||||||
!test/container
|
|
||||||
!test/e2e/testdata
|
|
||||||
!test/fixture
|
|
||||||
!test/remote
|
|
||||||
!hack/installers
|
|
||||||
!hack/gpg-wrapper.sh
|
|
||||||
!hack/git-verify-wrapper.sh
|
|
||||||
!hack/tool-versions.sh
|
|
||||||
!hack/install.sh
|
|
||||||
13
.gitattributes
vendored
13
.gitattributes
vendored
@@ -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
|
|
||||||
43
.github/ISSUE_TEMPLATE/new_dev_tool.md
vendored
43
.github/ISSUE_TEMPLATE/new_dev_tool.md
vendored
@@ -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 tool’s 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>
|
|
||||||
6
.github/ISSUE_TEMPLATE/release.md
vendored
6
.github/ISSUE_TEMPLATE/release.md
vendored
@@ -9,6 +9,12 @@ assignees: ''
|
|||||||
Target RC1 date: ___. __, ____
|
Target RC1 date: ___. __, ____
|
||||||
Target GA date: ___. __, ____
|
Target GA date: ___. __, ____
|
||||||
|
|
||||||
|
- [ ] Create new section in the [Release Planning doc](https://docs.google.com/document/d/1trJIomcgXcfvLw0aYnERrFWfPjQOfYMDJOCh1S8nMBc/edit?usp=sharing)
|
||||||
|
- [ ] Schedule a Release Planning meeting roughly two weeks before the scheduled Release freeze date by adding it to the community calendar (or delegate this task to someone with write access to the community calendar)
|
||||||
|
- [ ] Include Zoom link in the invite
|
||||||
|
- [ ] Post in #argo-cd and #argo-contributors one week before the meeting
|
||||||
|
- [ ] Post again one hour before the meeting
|
||||||
|
- [ ] At the meeting, remove issues/PRs from the project's column for that release which have not been “claimed” by at least one Approver (add it to the next column if Approver requests that)
|
||||||
- [ ] 1wk before feature freeze post in #argo-contributors that PRs must be merged by DD-MM-YYYY to be included in the release - ask approvers to drop items from milestone they can’t merge
|
- [ ] 1wk before feature freeze post in #argo-contributors that PRs must be merged by DD-MM-YYYY to be included in the release - ask approvers to drop items from milestone they can’t merge
|
||||||
- [ ] At least two days before RC1 date, draft RC blog post and submit it for review (or delegate this task)
|
- [ ] At least two days before RC1 date, draft RC blog post and submit it for review (or delegate this task)
|
||||||
- [ ] Cut RC1 (or delegate this task to an Approver and coordinate timing)
|
- [ ] Cut RC1 (or delegate this task to an Approver and coordinate timing)
|
||||||
|
|||||||
3
.github/cherry-pick-bot.yml
vendored
3
.github/cherry-pick-bot.yml
vendored
@@ -1,3 +0,0 @@
|
|||||||
enabled: true
|
|
||||||
preservePullRequestTitle: true
|
|
||||||
|
|
||||||
40
.github/dependabot.yml
vendored
40
.github/dependabot.yml
vendored
@@ -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: "/"
|
||||||
@@ -21,38 +16,3 @@ updates:
|
|||||||
directory: "/ui/"
|
directory: "/ui/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/ui-test/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
|
|
||||||
- package-ecosystem: "docker"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
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"
|
|
||||||
directory: "/test/container/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
|
|
||||||
- package-ecosystem: "docker"
|
|
||||||
directory: "/test/e2e/multiarch-container/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
|
|
||||||
- package-ecosystem: "docker"
|
|
||||||
directory: "/test/remote/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
|
|
||||||
- package-ecosystem: "docker"
|
|
||||||
directory: "/ui-test/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
|
|||||||
15
.github/pr-title-checker-config.json
vendored
15
.github/pr-title-checker-config.json
vendored
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"LABEL": {
|
|
||||||
"name": "title needs formatting",
|
|
||||||
"color": "EEEEEE"
|
|
||||||
},
|
|
||||||
"CHECKS": {
|
|
||||||
"prefixes": ["[Bot] docs: "],
|
|
||||||
"regexp": "^(feat|fix|docs|test|ci|chore)!?(\\(.*\\))?!?:.*"
|
|
||||||
},
|
|
||||||
"MESSAGES": {
|
|
||||||
"success": "PR title is valid",
|
|
||||||
"failure": "PR title is invalid",
|
|
||||||
"notice": "PR Title needs to pass regex '^(feat|fix|docs|test|ci|chore)!?(\\(.*\\))?!?:.*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
13
.github/pull_request_template.md
vendored
13
.github/pull_request_template.md
vendored
@@ -1,24 +1,17 @@
|
|||||||
<!--
|
|
||||||
Note on DCO:
|
Note on DCO:
|
||||||
|
|
||||||
If the DCO action in the integration test fails, one or more of your commits are not signed off. Please click on the *Details* link next to the DCO action for instructions on how to resolve this.
|
If the DCO action in the integration test fails, one or more of your commits are not signed off. Please click on the *Details* link next to the DCO action for instructions on how to resolve this.
|
||||||
-->
|
|
||||||
|
|
||||||
Checklist:
|
Checklist:
|
||||||
|
|
||||||
* [ ] Either (a) I've created an [enhancement proposal](https://github.com/argoproj/argo-cd/issues/new/choose) and discussed it with the community, (b) this is a bug fix, or (c) this does not need to be in the release notes.
|
* [ ] Either (a) I've created an [enhancement proposal](https://github.com/argoproj/argo-cd/issues/new/choose) and discussed it with the community, (b) this is a bug fix, or (c) this does not need to be in the release notes.
|
||||||
* [ ] The title of the PR states what changed and the related issues number (used for the release note).
|
* [ ] The title of the PR states what changed and the related issues number (used for the release note).
|
||||||
* [ ] The title of the PR conforms to the [Toolchain Guide](https://argo-cd.readthedocs.io/en/latest/developer-guide/toolchain-guide/#title-of-the-pr)
|
|
||||||
* [ ] I've included "Closes [ISSUE #]" or "Fixes [ISSUE #]" in the description to automatically close the associated issue.
|
* [ ] I've included "Closes [ISSUE #]" or "Fixes [ISSUE #]" in the description to automatically close the associated issue.
|
||||||
* [ ] I've updated both the CLI and UI to expose my feature, or I plan to submit a second PR with them.
|
* [ ] I've updated both the CLI and UI to expose my feature, or I plan to submit a second PR with them.
|
||||||
* [ ] Does this PR require documentation updates?
|
* [ ] Does this PR require documentation updates?
|
||||||
* [ ] I've updated documentation as required by this PR.
|
* [ ] I've updated documentation as required by this PR.
|
||||||
* [ ] I have signed off all my commits as required by [DCO](https://github.com/argoproj/argoproj/blob/master/community/CONTRIBUTING.md#legal)
|
|
||||||
* [ ] I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged.
|
|
||||||
* [ ] My build is green ([troubleshooting builds](https://argo-cd.readthedocs.io/en/latest/developer-guide/ci/)).
|
|
||||||
* [ ] My new feature complies with the [feature status](https://github.com/argoproj/argoproj/blob/master/community/feature-status.md) guidelines.
|
|
||||||
* [ ] I have added a brief description of why this PR is necessary and/or what this PR solves.
|
|
||||||
* [ ] Optional. My organization is added to USERS.md.
|
* [ ] Optional. My organization is added to USERS.md.
|
||||||
* [ ] Optional. For bug fixes, I've indicated what older releases this fix should be cherry-picked into (this may or may not happen depending on risk/complexity).
|
* [ ] I have signed off all my commits as required by [DCO](https://github.com/argoproj/argoproj/tree/master/community#contributing-to-argo)
|
||||||
|
* [ ] I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged.
|
||||||
|
* [ ] My build is green ([troubleshooting builds](https://argo-cd.readthedocs.io/en/latest/developer-guide/ci/)).
|
||||||
|
|
||||||
<!-- Please see [Contribution FAQs](https://argo-cd.readthedocs.io/en/latest/developer-guide/faq/) if you have questions about your pull-request. -->
|
|
||||||
|
|||||||
39
.github/workflows/README.md
vendored
39
.github/workflows/README.md
vendored
@@ -1,39 +0,0 @@
|
|||||||
# Workflows
|
|
||||||
|
|
||||||
| Workflow | Description |
|
|
||||||
|--------------------|----------------------------------------------------------------|
|
|
||||||
| ci-build.yaml | Build, lint, test, codegen, build-ui, analyze, e2e-test |
|
|
||||||
| codeql.yaml | CodeQL analysis |
|
|
||||||
| image-reuse.yaml | Build, push, and Sign container images |
|
|
||||||
| image.yaml | Build container image for PR's & publish for push events |
|
|
||||||
| init-release.yaml | Build manifests and version then create a PR for release branch|
|
|
||||||
| pr-title-check.yaml| Lint PR for semantic information |
|
|
||||||
| release.yaml | Build images, cli-binaries, provenances, and post actions |
|
|
||||||
| scorecard.yaml | Generate scorecard for supply-chain security |
|
|
||||||
| update-snyk.yaml | Scheduled snyk reports |
|
|
||||||
|
|
||||||
# Reusable workflows
|
|
||||||
|
|
||||||
## image-reuse.yaml
|
|
||||||
|
|
||||||
- The resuable workflow can be used to publish or build images with multiple container registries(Quay,GHCR, dockerhub), and then sign them with cosign when an image is published.
|
|
||||||
- A GO version `must` be specified e.g. 1.21
|
|
||||||
- The image name for each registry *must* contain the tag. Note: multiple tags are allowed for each registry using a CSV type.
|
|
||||||
- Multiple platforms can be specified e.g. linux/amd64,linux/arm64
|
|
||||||
- Images are not published by default. A boolean value must be set to `true` to push images.
|
|
||||||
- An optional target can be specified.
|
|
||||||
|
|
||||||
| Inputs | Description | Type | Required | Defaults |
|
|
||||||
|-------------------|-------------------------------------|-------------|----------|-----------------|
|
|
||||||
| go-version | Version of Go to be used | string | true | none |
|
|
||||||
| quay_image_name | Full image name and tag | CSV, string | false | none |
|
|
||||||
| ghcr_image_name | Full image name and tag | CSV, string | false | none |
|
|
||||||
| docker_image_name | Full image name and tag | CSV, string | false | none |
|
|
||||||
| platforms | Platforms to build (linux/amd64) | CSV, string | false | linux/amd64 |
|
|
||||||
| push | Whether to push image/s to registry | boolean | false | false |
|
|
||||||
| target | Target build stage | string | false | none |
|
|
||||||
|
|
||||||
| Outputs | Description | Type |
|
|
||||||
|-------------|------------------------------------------|-------|
|
|
||||||
|image-digest | Image digest of image container created | string|
|
|
||||||
|
|
||||||
264
.github/workflows/ci-build.yaml
vendored
264
.github/workflows/ci-build.yaml
vendored
@@ -1,5 +1,5 @@
|
|||||||
name: Integration tests
|
name: Integration tests
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
@@ -9,12 +9,10 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
- 'release-*'
|
|
||||||
|
|
||||||
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.18'
|
||||||
GOLANG_VERSION: '1.23.3'
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
@@ -24,65 +22,36 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
changes:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
backend: ${{ steps.filter.outputs.backend_any_changed }}
|
|
||||||
frontend: ${{ steps.filter.outputs.frontend_any_changed }}
|
|
||||||
docs: ${{ steps.filter.outputs.docs_any_changed }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
|
||||||
- uses: tj-actions/changed-files@bab30c2299617f6615ec02a68b9a40d10bd21366 # v45.0.5
|
|
||||||
id: filter
|
|
||||||
with:
|
|
||||||
# Any file which is not under docs/, ui/ or is not a markdown file is counted as a backend file
|
|
||||||
files_yaml: |
|
|
||||||
backend:
|
|
||||||
- '!ui/**'
|
|
||||||
- '!**.md'
|
|
||||||
- '!**/*.md'
|
|
||||||
- '!docs/**'
|
|
||||||
frontend:
|
|
||||||
- 'ui/**'
|
|
||||||
- Dockerfile
|
|
||||||
docs:
|
|
||||||
- 'docs/**'
|
|
||||||
check-go:
|
check-go:
|
||||||
name: Ensure Go modules synchronicity
|
name: Ensure Go modules synchronicity
|
||||||
if: ${{ needs.changes.outputs.backend == 'true' }}
|
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs:
|
|
||||||
- changes
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
- name: Setup Golang
|
- name: Setup Golang
|
||||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GOLANG_VERSION }}
|
go-version: ${{ env.GOLANG_VERSION }}
|
||||||
- name: Download all Go modules
|
- name: Download all Go modules
|
||||||
run: |
|
run: |
|
||||||
go mod download
|
go mod download
|
||||||
- name: Check for tidiness of go.mod and go.sum
|
- name: Check for tidyness of go.mod and go.sum
|
||||||
run: |
|
run: |
|
||||||
go mod tidy
|
go mod tidy
|
||||||
git diff --exit-code -- .
|
git diff --exit-code -- .
|
||||||
|
|
||||||
build-go:
|
build-go:
|
||||||
name: Build & cache Go code
|
name: Build & cache Go code
|
||||||
if: ${{ needs.changes.outputs.backend == 'true' }}
|
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs:
|
|
||||||
- changes
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
- name: Setup Golang
|
- name: Setup Golang
|
||||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GOLANG_VERSION }}
|
go-version: ${{ env.GOLANG_VERSION }}
|
||||||
- name: Restore go build cache
|
- name: Restore go build cache
|
||||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
uses: actions/cache@627f0f41f6904a5b1efbaed9f96d9eb58e92e920 # v3.2.4
|
||||||
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 }}
|
||||||
@@ -97,43 +66,37 @@ jobs:
|
|||||||
contents: read # for actions/checkout to fetch code
|
contents: read # for actions/checkout to fetch code
|
||||||
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
|
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
|
||||||
name: Lint Go code
|
name: Lint Go code
|
||||||
if: ${{ needs.changes.outputs.backend == 'true' }}
|
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs:
|
|
||||||
- changes
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
- name: Setup Golang
|
- name: Setup Golang
|
||||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GOLANG_VERSION }}
|
go-version: ${{ env.GOLANG_VERSION }}
|
||||||
- name: Run golangci-lint
|
- name: Run golangci-lint
|
||||||
uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1
|
uses: golangci/golangci-lint-action@0ad9a0988b3973e851ab0a07adf248ec2e100376 # v3.3.1
|
||||||
with:
|
with:
|
||||||
# renovate: datasource=go packageName=github.com/golangci/golangci-lint versioning=regex:^v(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?$
|
version: v1.46.2
|
||||||
version: v1.62.2
|
args: --timeout 10m --exclude SA5011 --verbose
|
||||||
args: --verbose
|
|
||||||
|
|
||||||
test-go:
|
test-go:
|
||||||
name: Run unit tests for Go packages
|
name: Run unit tests for Go packages
|
||||||
if: ${{ needs.changes.outputs.backend == 'true' }}
|
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs:
|
needs:
|
||||||
- build-go
|
- build-go
|
||||||
- changes
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
|
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- name: Create checkout directory
|
- name: Create checkout directory
|
||||||
run: mkdir -p ~/go/src/github.com/argoproj
|
run: mkdir -p ~/go/src/github.com/argoproj
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
- name: Create symlink in GOPATH
|
- name: Create symlink in GOPATH
|
||||||
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
|
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
|
||||||
- name: Setup Golang
|
- name: Setup Golang
|
||||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GOLANG_VERSION }}
|
go-version: ${{ env.GOLANG_VERSION }}
|
||||||
- name: Install required packages
|
- name: Install required packages
|
||||||
@@ -153,17 +116,13 @@ 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@627f0f41f6904a5b1efbaed9f96d9eb58e92e920 # v3.2.4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/go-build
|
path: ~/.cache/go-build
|
||||||
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
||||||
- name: Install all tools required for building & testing
|
- name: Install all tools required for building & testing
|
||||||
run: |
|
run: |
|
||||||
make install-test-tools-local
|
make install-test-tools-local
|
||||||
# We install kustomize in the dist directory
|
|
||||||
- name: Add dist to PATH
|
|
||||||
run: |
|
|
||||||
echo "/home/runner/work/argo-cd/argo-cd/dist" >> $GITHUB_PATH
|
|
||||||
- name: Setup git username and email
|
- name: Setup git username and email
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name "John Doe"
|
git config --global user.name "John Doe"
|
||||||
@@ -173,31 +132,34 @@ jobs:
|
|||||||
go mod download
|
go mod download
|
||||||
- name: Run all unit tests
|
- name: Run all unit tests
|
||||||
run: make test-local
|
run: make test-local
|
||||||
|
- name: Generate code coverage artifacts
|
||||||
|
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||||
|
with:
|
||||||
|
name: code-coverage
|
||||||
|
path: coverage.out
|
||||||
- name: Generate test results artifacts
|
- name: Generate test results artifacts
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||||
with:
|
with:
|
||||||
name: test-results
|
name: test-results
|
||||||
path: test-results
|
path: test-results/
|
||||||
|
|
||||||
test-go-race:
|
test-go-race:
|
||||||
name: Run unit tests with -race for Go packages
|
name: Run unit tests with -race, for Go packages
|
||||||
if: ${{ needs.changes.outputs.backend == 'true' }}
|
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs:
|
needs:
|
||||||
- build-go
|
- build-go
|
||||||
- changes
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
|
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- name: Create checkout directory
|
- name: Create checkout directory
|
||||||
run: mkdir -p ~/go/src/github.com/argoproj
|
run: mkdir -p ~/go/src/github.com/argoproj
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
- name: Create symlink in GOPATH
|
- name: Create symlink in GOPATH
|
||||||
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
|
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
|
||||||
- name: Setup Golang
|
- name: Setup Golang
|
||||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GOLANG_VERSION }}
|
go-version: ${{ env.GOLANG_VERSION }}
|
||||||
- name: Install required packages
|
- name: Install required packages
|
||||||
@@ -217,17 +179,13 @@ 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@627f0f41f6904a5b1efbaed9f96d9eb58e92e920 # v3.2.4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/go-build
|
path: ~/.cache/go-build
|
||||||
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
||||||
- name: Install all tools required for building & testing
|
- name: Install all tools required for building & testing
|
||||||
run: |
|
run: |
|
||||||
make install-test-tools-local
|
make install-test-tools-local
|
||||||
# We install kustomize in the dist directory
|
|
||||||
- name: Add dist to PATH
|
|
||||||
run: |
|
|
||||||
echo "/home/runner/work/argo-cd/argo-cd/dist" >> $GITHUB_PATH
|
|
||||||
- name: Setup git username and email
|
- name: Setup git username and email
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name "John Doe"
|
git config --global user.name "John Doe"
|
||||||
@@ -238,22 +196,19 @@ 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@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||||
with:
|
with:
|
||||||
name: race-results
|
name: race-results
|
||||||
path: test-results/
|
path: test-results/
|
||||||
|
|
||||||
codegen:
|
codegen:
|
||||||
name: Check changes to generated code
|
name: Check changes to generated code
|
||||||
if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.docs == 'true'}}
|
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs:
|
|
||||||
- changes
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
- name: Setup Golang
|
- name: Setup Golang
|
||||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GOLANG_VERSION }}
|
go-version: ${{ env.GOLANG_VERSION }}
|
||||||
- name: Create symlink in GOPATH
|
- name: Create symlink in GOPATH
|
||||||
@@ -277,10 +232,6 @@ jobs:
|
|||||||
make install-codegen-tools-local
|
make install-codegen-tools-local
|
||||||
make install-go-tools-local
|
make install-go-tools-local
|
||||||
working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
|
working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
|
||||||
# We install kustomize in the dist directory
|
|
||||||
- name: Add dist to PATH
|
|
||||||
run: |
|
|
||||||
echo "/home/runner/work/argo-cd/argo-cd/dist" >> $GITHUB_PATH
|
|
||||||
- name: Run codegen
|
- name: Run codegen
|
||||||
run: |
|
run: |
|
||||||
set -x
|
set -x
|
||||||
@@ -296,22 +247,17 @@ jobs:
|
|||||||
|
|
||||||
build-ui:
|
build-ui:
|
||||||
name: Build, test & lint UI code
|
name: Build, test & lint UI code
|
||||||
# We run UI logic for backend changes so that we have a complete set of coverage documents to send to codecov.
|
|
||||||
if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' }}
|
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs:
|
|
||||||
- changes
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
- name: Setup NodeJS
|
- name: Setup NodeJS
|
||||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
||||||
with:
|
with:
|
||||||
# renovate: datasource=node-version packageName=node versioning=node
|
node-version: '12.18.4'
|
||||||
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@627f0f41f6904a5b1efbaed9f96d9eb58e92e920 # v3.2.4
|
||||||
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 +272,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
|
||||||
@@ -335,86 +279,78 @@ jobs:
|
|||||||
|
|
||||||
analyze:
|
analyze:
|
||||||
name: Process & analyze test artifacts
|
name: Process & analyze test artifacts
|
||||||
if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' }}
|
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs:
|
needs:
|
||||||
- test-go
|
- test-go
|
||||||
- build-ui
|
- build-ui
|
||||||
- changes
|
|
||||||
- test-e2e
|
|
||||||
env:
|
env:
|
||||||
sonar_secret: ${{ secrets.SONAR_TOKEN }}
|
sonar_secret: ${{ secrets.SONAR_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Restore node dependency cache
|
- name: Restore node dependency cache
|
||||||
id: cache-dependencies
|
id: cache-dependencies
|
||||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
uses: actions/cache@627f0f41f6904a5b1efbaed9f96d9eb58e92e920 # v3.2.4
|
||||||
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') }}
|
||||||
- name: Remove other node_modules directory
|
- name: Remove other node_modules directory
|
||||||
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: Create test-results directory
|
||||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
run: |
|
||||||
|
mkdir -p test-results
|
||||||
|
- name: Get code coverage artifiact
|
||||||
|
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
|
||||||
with:
|
with:
|
||||||
name: e2e-code-coverage
|
name: code-coverage
|
||||||
path: e2e-code-coverage
|
- name: Get test result artifact
|
||||||
- name: Get unit test code coverage
|
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
|
||||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
|
||||||
with:
|
with:
|
||||||
name: test-results
|
name: test-results
|
||||||
path: test-results
|
path: test-results
|
||||||
- name: combine-go-coverage
|
|
||||||
# We generate coverage reports for all Argo CD components, but only the applicationset-controller,
|
|
||||||
# app-controller, repo-server, and commit-server report contain coverage data. The other components currently
|
|
||||||
# don't shut down gracefully, so no coverage data is produced. Once those components are fixed, we can add
|
|
||||||
# references to their coverage output directories.
|
|
||||||
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
|
|
||||||
- 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@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1
|
||||||
with:
|
with:
|
||||||
file: test-results/full-coverage.out
|
file: coverage.out
|
||||||
fail_ci_if_error: true
|
|
||||||
env:
|
|
||||||
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
|
SCANNER_VERSION: 4.2.0.1873
|
||||||
|
SCANNER_PATH: /tmp/cache/scanner
|
||||||
|
OS: linux
|
||||||
|
run: |
|
||||||
|
# We do not use the provided action, because it does contain an old
|
||||||
|
# version of the scanner, and also takes time to build.
|
||||||
|
set -e
|
||||||
|
mkdir -p ${SCANNER_PATH}
|
||||||
|
export SONAR_USER_HOME=${SCANNER_PATH}/.sonar
|
||||||
|
if [[ ! -x "${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner" ]]; then
|
||||||
|
curl -Ol https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SCANNER_VERSION}-${OS}.zip
|
||||||
|
unzip -qq -o sonar-scanner-cli-${SCANNER_VERSION}-${OS}.zip -d ${SCANNER_PATH}
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod +x ${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner
|
||||||
|
chmod +x ${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/jre/bin/java
|
||||||
|
|
||||||
|
# Explicitly set NODE_MODULES
|
||||||
|
export NODE_MODULES=${PWD}/ui/node_modules
|
||||||
|
export NODE_PATH=${PWD}/ui/node_modules
|
||||||
|
|
||||||
|
${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner
|
||||||
if: env.sonar_secret != ''
|
if: env.sonar_secret != ''
|
||||||
|
|
||||||
test-e2e:
|
test-e2e:
|
||||||
name: Run end-to-end tests
|
name: Run end-to-end tests
|
||||||
if: ${{ needs.changes.outputs.backend == 'true' }}
|
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
matrix:
|
||||||
k3s:
|
k3s-version: [v1.26.0, v1.25.4, v1.24.3, v1.23.3]
|
||||||
- version: v1.31.0
|
needs:
|
||||||
# We designate the latest version because we only collect code coverage for that version.
|
|
||||||
latest: true
|
|
||||||
- version: v1.30.4
|
|
||||||
latest: false
|
|
||||||
- version: v1.29.8
|
|
||||||
latest: false
|
|
||||||
- version: v1.28.13
|
|
||||||
latest: false
|
|
||||||
needs:
|
|
||||||
- build-go
|
- build-go
|
||||||
- changes
|
|
||||||
env:
|
env:
|
||||||
GOPATH: /home/runner/go
|
GOPATH: /home/runner/go
|
||||||
ARGOCD_FAKE_IN_CLUSTER: "true"
|
ARGOCD_FAKE_IN_CLUSTER: "true"
|
||||||
@@ -424,15 +360,15 @@ jobs:
|
|||||||
ARGOCD_E2E_K3S: "true"
|
ARGOCD_E2E_K3S: "true"
|
||||||
ARGOCD_IN_CI: "true"
|
ARGOCD_IN_CI: "true"
|
||||||
ARGOCD_E2E_APISERVER_PORT: "8088"
|
ARGOCD_E2E_APISERVER_PORT: "8088"
|
||||||
ARGOCD_APPLICATION_NAMESPACES: "argocd-e2e-external,argocd-e2e-external-2"
|
ARGOCD_APPLICATION_NAMESPACES: "argocd-e2e-external"
|
||||||
ARGOCD_SERVER: "127.0.0.1:8088"
|
ARGOCD_SERVER: "127.0.0.1:8088"
|
||||||
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
|
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
- name: Setup Golang
|
- name: Setup Golang
|
||||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GOLANG_VERSION }}
|
go-version: ${{ env.GOLANG_VERSION }}
|
||||||
- name: GH actions workaround - Kill XSP4 process
|
- name: GH actions workaround - Kill XSP4 process
|
||||||
@@ -440,7 +376,7 @@ jobs:
|
|||||||
sudo pkill mono || true
|
sudo pkill mono || true
|
||||||
- name: Install K3S
|
- name: Install K3S
|
||||||
env:
|
env:
|
||||||
INSTALL_K3S_VERSION: ${{ matrix.k3s.version }}+k3s1
|
INSTALL_K3S_VERSION: ${{ matrix.k3s-version }}+k3s1
|
||||||
run: |
|
run: |
|
||||||
set -x
|
set -x
|
||||||
curl -sfL https://get.k3s.io | sh -
|
curl -sfL https://get.k3s.io | sh -
|
||||||
@@ -448,10 +384,9 @@ jobs:
|
|||||||
sudo mkdir -p $HOME/.kube && sudo chown -R runner $HOME/.kube
|
sudo mkdir -p $HOME/.kube && sudo chown -R runner $HOME/.kube
|
||||||
sudo k3s kubectl config view --raw > $HOME/.kube/config
|
sudo k3s kubectl config view --raw > $HOME/.kube/config
|
||||||
sudo chown runner $HOME/.kube/config
|
sudo chown runner $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@627f0f41f6904a5b1efbaed9f96d9eb58e92e920 # v3.2.4
|
||||||
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,9 +412,9 @@ 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.35.3
|
||||||
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.7-alpine
|
||||||
- name: Create target directory for binaries in the build-process
|
- name: Create target directory for binaries in the build-process
|
||||||
run: |
|
run: |
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
@@ -492,7 +427,7 @@ jobs:
|
|||||||
# port 8080 which is not visible in netstat -tulpen, but still there
|
# port 8080 which is not visible in netstat -tulpen, but still there
|
||||||
# with a HTTP listener. We have API server listening on port 8088
|
# with a HTTP listener. We have API server listening on port 8088
|
||||||
# instead.
|
# instead.
|
||||||
make start-e2e-local COVERAGE_ENABLED=true 2>&1 | sed -r "s/[[:cntrl:]]\[[0-9]{1,3}m//g" > /tmp/e2e-server.log &
|
make start-e2e-local 2>&1 | sed -r "s/[[:cntrl:]]\[[0-9]{1,3}m//g" > /tmp/e2e-server.log &
|
||||||
count=1
|
count=1
|
||||||
until curl -f http://127.0.0.1:8088/healthz; do
|
until curl -f http://127.0.0.1:8088/healthz; do
|
||||||
sleep 10;
|
sleep 10;
|
||||||
@@ -506,40 +441,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -x
|
set -x
|
||||||
make test-e2e-local
|
make test-e2e-local
|
||||||
goreman run stop-all || echo "goreman trouble"
|
|
||||||
sleep 30
|
|
||||||
- name: Upload e2e coverage report
|
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
|
||||||
with:
|
|
||||||
name: e2e-code-coverage
|
|
||||||
path: /tmp/coverage
|
|
||||||
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@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||||
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
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
|
|
||||||
# workaround for status checks -- check this one job instead of each individual E2E job in the matrix
|
|
||||||
# this allows us to skip the entire matrix when it doesn't need to run while still having accurate status checks
|
|
||||||
# see:
|
|
||||||
# https://github.com/argoproj/argo-workflows/pull/12006
|
|
||||||
# https://github.com/orgs/community/discussions/9141#discussioncomment-2296809
|
|
||||||
# https://github.com/orgs/community/discussions/26822#discussioncomment-3305794
|
|
||||||
test-e2e-composite-result:
|
|
||||||
name: E2E Tests - Composite result
|
|
||||||
if: ${{ always() }}
|
|
||||||
needs:
|
|
||||||
- test-e2e
|
|
||||||
- changes
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- run: |
|
|
||||||
result="${{ needs.test-e2e.result }}"
|
|
||||||
# mark as successful even if skipped
|
|
||||||
if [[ $result == "success" || $result == "skipped" ]]; then
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
18
.github/workflows/codeql.yml
vendored
18
.github/workflows/codeql.yml
vendored
@@ -5,7 +5,6 @@ on:
|
|||||||
# Secrets aren't available for dependabot on push. https://docs.github.com/en/enterprise-cloud@latest/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/troubleshooting-the-codeql-workflow#error-403-resource-not-accessible-by-integration-when-using-dependabot
|
# Secrets aren't available for dependabot on push. https://docs.github.com/en/enterprise-cloud@latest/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/troubleshooting-the-codeql-workflow#error-403-resource-not-accessible-by-integration-when-using-dependabot
|
||||||
branches-ignore:
|
branches-ignore:
|
||||||
- 'dependabot/**'
|
- 'dependabot/**'
|
||||||
- 'cherry-pick-*'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 19 * * 0'
|
- cron: '0 19 * * 0'
|
||||||
@@ -23,23 +22,18 @@ 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
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
|
|
||||||
# Use correct go version. https://github.com/github/codeql-action/issues/1842#issuecomment-1704398087
|
|
||||||
- name: Setup Golang
|
|
||||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@8fcfedf57053e09257688fce7a0beeb18b1b9ae3 # v2.17.2
|
uses: github/codeql-action/init@8aff97f12c99086bdb92ff62ae06dbbcdf07941b # v2.1.33
|
||||||
# Override language selection by uncommenting this and choosing your languages
|
# Override language selection by uncommenting this and choosing your languages
|
||||||
# with:
|
# with:
|
||||||
# languages: go, javascript, csharp, python, cpp, java
|
# languages: go, javascript, csharp, python, cpp, java
|
||||||
@@ -47,7 +41,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@8fcfedf57053e09257688fce7a0beeb18b1b9ae3 # v2.17.2
|
uses: github/codeql-action/autobuild@8aff97f12c99086bdb92ff62ae06dbbcdf07941b # v2.1.33
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
@@ -61,4 +55,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@8fcfedf57053e09257688fce7a0beeb18b1b9ae3 # v2.17.2
|
uses: github/codeql-action/analyze@8aff97f12c99086bdb92ff62ae06dbbcdf07941b # v2.1.33
|
||||||
|
|||||||
171
.github/workflows/image-reuse.yaml
vendored
171
.github/workflows/image-reuse.yaml
vendored
@@ -1,171 +0,0 @@
|
|||||||
name: Publish and Sign Container Image
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
go-version:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
quay_image_name:
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
ghcr_image_name:
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
docker_image_name:
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
platforms:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
default: linux/amd64
|
|
||||||
push:
|
|
||||||
required: true
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
target:
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
|
|
||||||
secrets:
|
|
||||||
quay_username:
|
|
||||||
required: false
|
|
||||||
quay_password:
|
|
||||||
required: false
|
|
||||||
ghcr_username:
|
|
||||||
required: false
|
|
||||||
ghcr_password:
|
|
||||||
required: false
|
|
||||||
docker_username:
|
|
||||||
required: false
|
|
||||||
docker_password:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
outputs:
|
|
||||||
image-digest:
|
|
||||||
description: "sha256 digest of container image"
|
|
||||||
value: ${{ jobs.publish.outputs.image-digest }}
|
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write # Used to push images to `ghcr.io` if used.
|
|
||||||
id-token: write # Needed to create an OIDC token for keyless signing
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
outputs:
|
|
||||||
image-digest: ${{ steps.image.outputs.digest }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
if: ${{ github.ref_type == 'tag'}}
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
|
||||||
if: ${{ github.ref_type != 'tag'}}
|
|
||||||
|
|
||||||
- name: Setup Golang
|
|
||||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
|
||||||
with:
|
|
||||||
go-version: ${{ inputs.go-version }}
|
|
||||||
|
|
||||||
- name: Install cosign
|
|
||||||
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
|
|
||||||
|
|
||||||
- uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
|
|
||||||
- uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1
|
|
||||||
|
|
||||||
- name: Setup tags for container image as a CSV type
|
|
||||||
run: |
|
|
||||||
IMAGE_TAGS=$(for str in \
|
|
||||||
${{ inputs.quay_image_name }} \
|
|
||||||
${{ inputs.ghcr_image_name }} \
|
|
||||||
${{ inputs.docker_image_name}}; do
|
|
||||||
echo -n "${str}",;done | sed 's/,$//')
|
|
||||||
|
|
||||||
echo $IMAGE_TAGS
|
|
||||||
echo "TAGS=$IMAGE_TAGS" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Setup image namespace for signing, strip off the tag
|
|
||||||
run: |
|
|
||||||
TAGS=$(for tag in \
|
|
||||||
${{ inputs.quay_image_name }} \
|
|
||||||
${{ inputs.ghcr_image_name }} \
|
|
||||||
${{ inputs.docker_image_name}}; do
|
|
||||||
echo -n "${tag}" | awk -F ":" '{print $1}' -;done)
|
|
||||||
|
|
||||||
echo $TAGS
|
|
||||||
echo 'SIGNING_TAGS<<EOF' >> $GITHUB_ENV
|
|
||||||
echo $TAGS >> $GITHUB_ENV
|
|
||||||
echo 'EOF' >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Login to Quay.io
|
|
||||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
|
||||||
with:
|
|
||||||
registry: quay.io
|
|
||||||
username: ${{ secrets.quay_username }}
|
|
||||||
password: ${{ secrets.quay_password }}
|
|
||||||
if: ${{ inputs.quay_image_name && inputs.push }}
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ secrets.ghcr_username }}
|
|
||||||
password: ${{ secrets.ghcr_password }}
|
|
||||||
if: ${{ inputs.ghcr_image_name && inputs.push }}
|
|
||||||
|
|
||||||
- name: Login to dockerhub Container Registry
|
|
||||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.docker_username }}
|
|
||||||
password: ${{ secrets.docker_password }}
|
|
||||||
if: ${{ inputs.docker_image_name && inputs.push }}
|
|
||||||
|
|
||||||
- name: Set up build args for container image
|
|
||||||
run: |
|
|
||||||
echo "GIT_TAG=$(if [ -z "`git status --porcelain`" ]; then git describe --exact-match --tags HEAD 2>/dev/null; fi)" >> $GITHUB_ENV
|
|
||||||
echo "GIT_COMMIT=$(git rev-parse HEAD)" >> $GITHUB_ENV
|
|
||||||
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
|
|
||||||
echo "GIT_TREE_STATE=$(if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Free Disk Space (Ubuntu)
|
|
||||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
|
||||||
with:
|
|
||||||
large-packages: false
|
|
||||||
docker-images: false
|
|
||||||
swap-storage: false
|
|
||||||
tool-cache: false
|
|
||||||
|
|
||||||
- name: Build and push container image
|
|
||||||
id: image
|
|
||||||
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 #v6.10.0
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: ${{ inputs.platforms }}
|
|
||||||
push: ${{ inputs.push }}
|
|
||||||
tags: ${{ env.TAGS }}
|
|
||||||
target: ${{ inputs.target }}
|
|
||||||
provenance: false
|
|
||||||
sbom: false
|
|
||||||
build-args: |
|
|
||||||
GIT_TAG=${{env.GIT_TAG}}
|
|
||||||
GIT_COMMIT=${{env.GIT_COMMIT}}
|
|
||||||
BUILD_DATE=${{env.BUILD_DATE}}
|
|
||||||
GIT_TREE_STATE=${{env.GIT_TREE_STATE}}
|
|
||||||
|
|
||||||
- name: Sign container images
|
|
||||||
run: |
|
|
||||||
for signing_tag in $SIGNING_TAGS; do
|
|
||||||
cosign sign \
|
|
||||||
-a "repo=${{ github.repository }}" \
|
|
||||||
-a "workflow=${{ github.workflow }}" \
|
|
||||||
-a "sha=${{ github.sha }}" \
|
|
||||||
-y \
|
|
||||||
"$signing_tag"@${{ steps.image.outputs.digest }}
|
|
||||||
done
|
|
||||||
if: ${{ inputs.push }}
|
|
||||||
144
.github/workflows/image.yaml
vendored
144
.github/workflows/image.yaml
vendored
@@ -9,111 +9,97 @@ on:
|
|||||||
- master
|
- master
|
||||||
types: [ labeled, unlabeled, opened, synchronize, reopened ]
|
types: [ labeled, unlabeled, opened, synchronize, reopened ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
GOLANG_VERSION: '1.18'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
permissions: {}
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
set-vars:
|
publish:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: write # for git to push upgrade commit if not already deployed
|
||||||
|
packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags
|
||||||
if: github.repository == 'argoproj/argo-cd'
|
if: github.repository == 'argoproj/argo-cd'
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
outputs:
|
env:
|
||||||
image-tag: ${{ steps.image.outputs.tag}}
|
GOPATH: /home/runner/work/argo-cd/argo-cd
|
||||||
platforms: ${{ steps.platforms.outputs.platforms }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
- uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GOLANG_VERSION }}
|
||||||
|
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
|
with:
|
||||||
|
path: src/github.com/argoproj/argo-cd
|
||||||
|
|
||||||
- name: Set image tag for ghcr
|
# get image tag
|
||||||
run: echo "tag=$(cat ./VERSION)-${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
|
- run: echo "tag=$(cat ./VERSION)-${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
|
||||||
|
working-directory: ./src/github.com/argoproj/argo-cd
|
||||||
id: image
|
id: image
|
||||||
|
|
||||||
- name: Determine image platforms to use
|
# login
|
||||||
id: platforms
|
- run: |
|
||||||
run: |
|
docker login ghcr.io --username $USERNAME --password-stdin <<< "$PASSWORD"
|
||||||
|
docker login quay.io --username "$DOCKER_USERNAME" --password-stdin <<< "$DOCKER_TOKEN"
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
env:
|
||||||
|
USERNAME: ${{ github.actor }}
|
||||||
|
PASSWORD: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
DOCKER_USERNAME: ${{ secrets.RELEASE_QUAY_USERNAME }}
|
||||||
|
DOCKER_TOKEN: ${{ secrets.RELEASE_QUAY_TOKEN }}
|
||||||
|
|
||||||
|
# build
|
||||||
|
- uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0
|
||||||
|
- uses: docker/setup-buildx-action@15c905b16b06416d2086efa066dd8e3a35cc7f98 # v2.4.0
|
||||||
|
- run: |
|
||||||
IMAGE_PLATFORMS=linux/amd64
|
IMAGE_PLATFORMS=linux/amd64
|
||||||
if [[ "${{ github.event_name }}" == "push" || "${{ contains(github.event.pull_request.labels.*.name, 'test-multi-image') }}" == "true" ]]
|
if [[ "${{ github.event_name }}" == "push" || "${{ contains(github.event.pull_request.labels.*.name, 'test-arm-image') }}" == "true" ]]
|
||||||
then
|
then
|
||||||
IMAGE_PLATFORMS=linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
|
IMAGE_PLATFORMS=linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
|
||||||
fi
|
fi
|
||||||
echo "Building image for platforms: $IMAGE_PLATFORMS"
|
echo "Building image for platforms: $IMAGE_PLATFORMS"
|
||||||
echo "platforms=$IMAGE_PLATFORMS" >> $GITHUB_OUTPUT
|
docker buildx build --platform $IMAGE_PLATFORMS --sbom=false --provenance=false --push="${{ github.event_name == 'push' }}" \
|
||||||
|
-t ghcr.io/argoproj/argo-cd/argocd:${{ steps.image.outputs.tag }} \
|
||||||
|
-t quay.io/argoproj/argocd:latest .
|
||||||
|
working-directory: ./src/github.com/argoproj/argo-cd
|
||||||
|
|
||||||
build-only:
|
# sign container images
|
||||||
needs: [set-vars]
|
- name: Install cosign
|
||||||
permissions:
|
uses: sigstore/cosign-installer@9becc617647dfa20ae7b1151972e9b3a2c338a2b # v2.8.1
|
||||||
contents: read
|
with:
|
||||||
packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags
|
cosign-release: 'v1.13.1'
|
||||||
id-token: write # for creating OIDC tokens for signing.
|
|
||||||
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name != 'push' }}
|
|
||||||
uses: ./.github/workflows/image-reuse.yaml
|
|
||||||
with:
|
|
||||||
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
|
|
||||||
# renovate: datasource=golang-version packageName=golang
|
|
||||||
go-version: 1.23.3
|
|
||||||
platforms: ${{ needs.set-vars.outputs.platforms }}
|
|
||||||
push: false
|
|
||||||
|
|
||||||
build-and-publish:
|
- name: Install crane to get digest of image
|
||||||
needs: [set-vars]
|
uses: imjasonh/setup-crane@e82f1b9a8007d399333baba4d75915558e9fb6a4
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags
|
|
||||||
id-token: write # for creating OIDC tokens for signing.
|
|
||||||
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
|
|
||||||
uses: ./.github/workflows/image-reuse.yaml
|
|
||||||
with:
|
|
||||||
quay_image_name: quay.io/argoproj/argocd:latest
|
|
||||||
ghcr_image_name: ghcr.io/argoproj/argo-cd/argocd:${{ needs.set-vars.outputs.image-tag }}
|
|
||||||
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
|
|
||||||
# renovate: datasource=golang-version packageName=golang
|
|
||||||
go-version: 1.23.3
|
|
||||||
platforms: ${{ needs.set-vars.outputs.platforms }}
|
|
||||||
push: true
|
|
||||||
secrets:
|
|
||||||
quay_username: ${{ secrets.RELEASE_QUAY_USERNAME }}
|
|
||||||
quay_password: ${{ secrets.RELEASE_QUAY_TOKEN }}
|
|
||||||
ghcr_username: ${{ github.actor }}
|
|
||||||
ghcr_password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
build-and-publish-provenance: # Push attestations to GHCR, latest image is polluting quay.io
|
- name: Get digest of image
|
||||||
needs:
|
run: |
|
||||||
- build-and-publish
|
echo "IMAGE_DIGEST=$(crane digest quay.io/argoproj/argocd:latest)" >> $GITHUB_ENV
|
||||||
permissions:
|
|
||||||
actions: read # for detecting the Github Actions environment.
|
|
||||||
id-token: write # for creating OIDC tokens for signing.
|
|
||||||
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
|
|
||||||
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
|
|
||||||
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
|
|
||||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
|
|
||||||
with:
|
|
||||||
image: ghcr.io/argoproj/argo-cd/argocd
|
|
||||||
digest: ${{ needs.build-and-publish.outputs.image-digest }}
|
|
||||||
registry-username: ${{ github.actor }}
|
|
||||||
secrets:
|
|
||||||
registry-password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
Deploy:
|
- name: Sign Argo CD latest image
|
||||||
needs:
|
run: |
|
||||||
- build-and-publish
|
cosign sign --key env://COSIGN_PRIVATE_KEY quay.io/argoproj/argocd@${{ env.IMAGE_DIGEST }}
|
||||||
- set-vars
|
# Displays the public key to share.
|
||||||
permissions:
|
cosign public-key --key env://COSIGN_PRIVATE_KEY
|
||||||
contents: write # for git to push upgrade commit if not already deployed
|
env:
|
||||||
packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags
|
COSIGN_PRIVATE_KEY: ${{secrets.COSIGN_PRIVATE_KEY}}
|
||||||
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
|
COSIGN_PASSWORD: ${{secrets.COSIGN_PASSWORD}}
|
||||||
runs-on: ubuntu-22.04
|
if: ${{ github.event_name == 'push' }}
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
# deploy
|
||||||
- run: git clone "https://$TOKEN@github.com/argoproj/argoproj-deployments"
|
- run: git clone "https://$TOKEN@github.com/argoproj/argoproj-deployments"
|
||||||
|
if: github.event_name == 'push'
|
||||||
env:
|
env:
|
||||||
TOKEN: ${{ secrets.TOKEN }}
|
TOKEN: ${{ secrets.TOKEN }}
|
||||||
- run: |
|
- run: |
|
||||||
docker run -u $(id -u):$(id -g) -v $(pwd):/src -w /src --rm -t ghcr.io/argoproj/argo-cd/argocd:${{ needs.set-vars.outputs.image-tag }} kustomize edit set image quay.io/argoproj/argocd=ghcr.io/argoproj/argo-cd/argocd:${{ needs.set-vars.outputs.image-tag }}
|
docker run -u $(id -u):$(id -g) -v $(pwd):/src -w /src --rm -t ghcr.io/argoproj/argo-cd/argocd:${{ steps.image.outputs.tag }} kustomize edit set image quay.io/argoproj/argocd=ghcr.io/argoproj/argo-cd/argocd:${{ steps.image.outputs.tag }}
|
||||||
git config --global user.email 'ci@argoproj.com'
|
git config --global user.email 'ci@argoproj.com'
|
||||||
git config --global user.name 'CI'
|
git config --global user.name 'CI'
|
||||||
git diff --exit-code && echo 'Already deployed' || (git commit -am 'Upgrade argocd to ${{ needs.set-vars.outputs.image-tag }}' && git push)
|
git diff --exit-code && echo 'Already deployed' || (git commit -am 'Upgrade argocd to ${{ steps.image.outputs.tag }}' && git push)
|
||||||
|
if: github.event_name == 'push'
|
||||||
working-directory: argoproj-deployments/argocd
|
working-directory: argoproj-deployments/argocd
|
||||||
|
# TODO: clean up old images once github supports it: https://github.community/t5/How-to-use-Git-and-GitHub/Deleting-images-from-GitHub-Package-Registry/m-p/41202/thread-id/9811
|
||||||
|
|||||||
77
.github/workflows/init-release.yaml
vendored
77
.github/workflows/init-release.yaml
vendored
@@ -1,77 +0,0 @@
|
|||||||
name: Init ArgoCD Release
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
TARGET_BRANCH:
|
|
||||||
description: 'TARGET_BRANCH to checkout (e.g. release-2.5)'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
|
|
||||||
TARGET_VERSION:
|
|
||||||
description: 'TARGET_VERSION to build manifests (e.g. 2.5.0-rc1) Note: the `v` prefix is not used'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
prepare-release:
|
|
||||||
permissions:
|
|
||||||
contents: write # for peter-evans/create-pull-request to create branch
|
|
||||||
pull-requests: write # for peter-evans/create-pull-request to create a PR
|
|
||||||
name: Automatically generate version and manifests on ${{ inputs.TARGET_BRANCH }}
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
ref: ${{ inputs.TARGET_BRANCH }}
|
|
||||||
|
|
||||||
- name: Check if TARGET_VERSION is well formed.
|
|
||||||
run: |
|
|
||||||
set -xue
|
|
||||||
# Target version must not contain 'v' prefix
|
|
||||||
if echo "${{ inputs.TARGET_VERSION }}" | grep -e '^v'; then
|
|
||||||
echo "::error::Target version '${{ inputs.TARGET_VERSION }}' should not begin with a 'v' prefix, refusing to continue." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Create VERSION information
|
|
||||||
run: |
|
|
||||||
set -ue
|
|
||||||
echo "Bumping version from $(cat VERSION) to ${{ inputs.TARGET_VERSION }}"
|
|
||||||
echo "${{ inputs.TARGET_VERSION }}" > VERSION
|
|
||||||
|
|
||||||
# We install kustomize in the dist directory
|
|
||||||
- name: Add dist to PATH
|
|
||||||
run: |
|
|
||||||
echo "/home/runner/work/argo-cd/argo-cd/dist" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- name: Generate new set of manifests
|
|
||||||
run: |
|
|
||||||
set -ue
|
|
||||||
make install-codegen-tools-local
|
|
||||||
make manifests-local VERSION=${{ inputs.TARGET_VERSION }}
|
|
||||||
git diff
|
|
||||||
|
|
||||||
- name: Generate version compatibility table
|
|
||||||
run: |
|
|
||||||
git stash
|
|
||||||
bash hack/update-supported-versions.sh
|
|
||||||
git add -u .
|
|
||||||
git stash pop
|
|
||||||
|
|
||||||
- name: Create pull request
|
|
||||||
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
|
|
||||||
with:
|
|
||||||
commit-message: "Bump version to ${{ inputs.TARGET_VERSION }}"
|
|
||||||
title: "Bump version to ${{ inputs.TARGET_VERSION }} on ${{ inputs.TARGET_BRANCH }} branch"
|
|
||||||
body: Updating VERSION and manifests to ${{ inputs.TARGET_VERSION }}
|
|
||||||
branch: update-version
|
|
||||||
branch-suffix: random
|
|
||||||
signoff: true
|
|
||||||
labels: release
|
|
||||||
|
|
||||||
|
|
||||||
32
.github/workflows/pr-title-check.yml
vendored
32
.github/workflows/pr-title-check.yml
vendored
@@ -2,11 +2,15 @@ name: "Lint PR"
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [opened, edited, reopened, synchronize]
|
types:
|
||||||
|
- opened
|
||||||
|
- edited
|
||||||
|
- synchronize
|
||||||
|
|
||||||
# IMPORTANT: No checkout actions, scripts, or builds should be added to this workflow. Permissions should always be used
|
# IMPORTANT: No checkout actions, scripts, or builds should be added to this workflow. Permissions should always be used
|
||||||
# with extreme caution. https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target
|
# with extreme caution.
|
||||||
permissions: {}
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
# PR updates can happen in quick succession leading to this
|
# PR updates can happen in quick succession leading to this
|
||||||
# workflow being trigger a number of times. This limits it
|
# workflow being trigger a number of times. This limits it
|
||||||
@@ -14,16 +18,24 @@ permissions: {}
|
|||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
validate:
|
main:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
pull-requests: read # for amannn/action-semantic-pull-request to analyze PRs
|
||||||
pull-requests: read
|
statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR
|
||||||
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
|
# IMPORTANT: Carefully review changes when updating this action. Using the pull_request_target event requires caution.
|
||||||
|
- uses: amannn/action-semantic-pull-request@01d5fd8a8ebb9aafe902c40c53f0f4744f7381eb # v5.0.2
|
||||||
with:
|
with:
|
||||||
|
types: |
|
||||||
|
feat
|
||||||
|
fix
|
||||||
|
docs
|
||||||
|
test
|
||||||
|
ci
|
||||||
|
chore
|
||||||
|
[Bot] docs
|
||||||
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
configuration_path: ".github/pr-title-checker-config.json"
|
|
||||||
|
|||||||
504
.github/workflows/release.yaml
vendored
504
.github/workflows/release.yaml
vendored
@@ -1,160 +1,267 @@
|
|||||||
name: Publish ArgoCD Release
|
name: Create ArgoCD release
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- "release-v*"
|
||||||
- '!v2.4*'
|
- "!release-v1.5*"
|
||||||
- '!v2.5*'
|
- "!release-v1.4*"
|
||||||
- '!v2.6*'
|
- "!release-v1.3*"
|
||||||
|
- "!release-v1.2*"
|
||||||
permissions: {}
|
- "!release-v1.1*"
|
||||||
|
- "!release-v1.0*"
|
||||||
|
- "!release-v0*"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# renovate: datasource=golang-version packageName=golang
|
GOLANG_VERSION: '1.18'
|
||||||
GOLANG_VERSION: '1.23.3' # Note: go-version must also be set in job argocd-image.with.go-version
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
argocd-image:
|
prepare-release:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: write # To push changes to release branch
|
||||||
id-token: write # for creating OIDC tokens for signing.
|
name: Perform automatic release on trigger ${{ github.ref }}
|
||||||
packages: write # used to push images to `ghcr.io` if used.
|
|
||||||
if: github.repository == 'argoproj/argo-cd'
|
|
||||||
uses: ./.github/workflows/image-reuse.yaml
|
|
||||||
with:
|
|
||||||
quay_image_name: quay.io/argoproj/argocd:${{ github.ref_name }}
|
|
||||||
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
|
|
||||||
# renovate: datasource=golang-version packageName=golang
|
|
||||||
go-version: 1.23.3
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
|
|
||||||
push: true
|
|
||||||
secrets:
|
|
||||||
quay_username: ${{ secrets.RELEASE_QUAY_USERNAME }}
|
|
||||||
quay_password: ${{ secrets.RELEASE_QUAY_TOKEN }}
|
|
||||||
|
|
||||||
argocd-image-provenance:
|
|
||||||
needs: [argocd-image]
|
|
||||||
permissions:
|
|
||||||
actions: read # for detecting the Github Actions environment.
|
|
||||||
id-token: write # for creating OIDC tokens for signing.
|
|
||||||
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
|
|
||||||
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
|
|
||||||
if: github.repository == 'argoproj/argo-cd'
|
|
||||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
|
|
||||||
with:
|
|
||||||
image: quay.io/argoproj/argocd
|
|
||||||
digest: ${{ needs.argocd-image.outputs.image-digest }}
|
|
||||||
secrets:
|
|
||||||
registry-username: ${{ secrets.RELEASE_QUAY_USERNAME }}
|
|
||||||
registry-password: ${{ secrets.RELEASE_QUAY_TOKEN }}
|
|
||||||
|
|
||||||
goreleaser:
|
|
||||||
needs:
|
|
||||||
- argocd-image
|
|
||||||
- argocd-image-provenance
|
|
||||||
permissions:
|
|
||||||
contents: write # used for uploading assets
|
|
||||||
if: github.repository == 'argoproj/argo-cd'
|
if: github.repository == 'argoproj/argo-cd'
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
outputs:
|
env:
|
||||||
hashes: ${{ steps.hash.outputs.hashes }}
|
# The name of the tag as supplied by the GitHub event
|
||||||
|
SOURCE_TAG: ${{ github.ref }}
|
||||||
|
# The image namespace where Docker image will be published to
|
||||||
|
IMAGE_NAMESPACE: quay.io/argoproj
|
||||||
|
# Whether to create & push image and release assets
|
||||||
|
DRY_RUN: false
|
||||||
|
# Whether a draft release should be created, instead of public one
|
||||||
|
DRAFT_RELEASE: false
|
||||||
|
# Whether to update homebrew with this release as well
|
||||||
|
# Set RELEASE_HOMEBREW_TOKEN secret in repository for this to work - needs
|
||||||
|
# access to public repositories
|
||||||
|
UPDATE_HOMEBREW: false
|
||||||
|
# Name of the GitHub user for Git config
|
||||||
|
GIT_USERNAME: argo-bot
|
||||||
|
# E-Mail of the GitHub user for Git config
|
||||||
|
GIT_EMAIL: argoproj@gmail.com
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Fetch all tags
|
- name: Check if the published tag is well formed and setup vars
|
||||||
run: git fetch --force --tags
|
|
||||||
|
|
||||||
- name: Setup Golang
|
|
||||||
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
|
# Target version must match major.minor.patch and optional -rcX suffix
|
||||||
|
# where X must be a number.
|
||||||
- name: Set environment variables for ldflags
|
TARGET_VERSION=${SOURCE_TAG#*release-v}
|
||||||
id: set_ldflag
|
if ! echo "${TARGET_VERSION}" | egrep '^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)*$'; then
|
||||||
run: |
|
echo "::error::Target version '${TARGET_VERSION}' is malformed, refusing to continue." >&2
|
||||||
echo "KUBECTL_VERSION=$(go list -m k8s.io/client-go | head -n 1 | rev | cut -d' ' -f1 | rev)" >> $GITHUB_ENV
|
exit 1
|
||||||
echo "GIT_TREE_STATE=$(if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Free Disk Space (Ubuntu)
|
|
||||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
|
||||||
with:
|
|
||||||
large-packages: false
|
|
||||||
docker-images: false
|
|
||||||
swap-storage: false
|
|
||||||
tool-cache: false
|
|
||||||
|
|
||||||
- name: Run GoReleaser
|
|
||||||
uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0
|
|
||||||
id: run-goreleaser
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
args: release --clean --timeout 55m
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
KUBECTL_VERSION: ${{ env.KUBECTL_VERSION }}
|
|
||||||
GIT_TREE_STATE: ${{ env.GIT_TREE_STATE }}
|
|
||||||
|
|
||||||
- name: Generate subject for provenance
|
|
||||||
id: hash
|
|
||||||
env:
|
|
||||||
ARTIFACTS: "${{ steps.run-goreleaser.outputs.artifacts }}"
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
hashes=$(echo $ARTIFACTS | jq --raw-output '.[] | {name, "digest": (.extra.Digest // .extra.Checksum)} | select(.digest) | {digest} + {name} | join(" ") | sub("^sha256:";"")' | base64 -w0)
|
|
||||||
if test "$hashes" = ""; then # goreleaser < v1.13.0
|
|
||||||
checksum_file=$(echo "$ARTIFACTS" | jq -r '.[] | select (.type=="Checksum") | .path')
|
|
||||||
hashes=$(cat $checksum_file | base64 -w0)
|
|
||||||
fi
|
fi
|
||||||
echo "hashes=$hashes" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
goreleaser-provenance:
|
# Target branch is the release branch we're going to operate on
|
||||||
needs: [goreleaser]
|
# Its name is 'release-<major>.<minor>'
|
||||||
permissions:
|
TARGET_BRANCH="release-${TARGET_VERSION%\.[0-9]*}"
|
||||||
actions: read # for detecting the Github Actions environment
|
|
||||||
id-token: write # Needed for provenance signing and ID
|
|
||||||
contents: write # Needed for release uploads
|
|
||||||
if: github.repository == 'argoproj/argo-cd'
|
|
||||||
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
|
|
||||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
|
|
||||||
with:
|
|
||||||
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
|
|
||||||
provenance-name: "argocd-cli.intoto.jsonl"
|
|
||||||
upload-assets: true
|
|
||||||
|
|
||||||
generate-sbom:
|
# The release tag is the source tag, minus the release- prefix
|
||||||
name: Create SBOM and generate hash
|
RELEASE_TAG="${SOURCE_TAG#*release-}"
|
||||||
needs:
|
|
||||||
- argocd-image
|
# Whether this is a pre-release (indicated by -rc suffix)
|
||||||
- goreleaser
|
PRE_RELEASE=false
|
||||||
permissions:
|
if echo "${RELEASE_TAG}" | egrep -- '-rc[0-9]+$'; then
|
||||||
contents: write # Needed for release uploads
|
PRE_RELEASE=true
|
||||||
outputs:
|
fi
|
||||||
hashes: ${{ steps.sbom-hash.outputs.hashes}}
|
|
||||||
if: github.repository == 'argoproj/argo-cd'
|
# We must not have a release trigger within the same release branch,
|
||||||
runs-on: ubuntu-22.04
|
# because that means a release for this branch is already running.
|
||||||
steps:
|
if git tag -l | grep "release-v${TARGET_VERSION%\.[0-9]*}" | grep -v "release-v${TARGET_VERSION}"; then
|
||||||
- name: Checkout code
|
echo "::error::Another release for branch ${TARGET_BRANCH} is currently in progress."
|
||||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
exit 1
|
||||||
with:
|
fi
|
||||||
fetch-depth: 0
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
# Ensure that release do not yet exist
|
||||||
|
if git rev-parse ${RELEASE_TAG}; then
|
||||||
|
echo "::error::Release tag ${RELEASE_TAG} already exists in repository. Refusing to continue."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Make the variables available in follow-up steps
|
||||||
|
echo "TARGET_VERSION=${TARGET_VERSION}" >> $GITHUB_ENV
|
||||||
|
echo "TARGET_BRANCH=${TARGET_BRANCH}" >> $GITHUB_ENV
|
||||||
|
echo "RELEASE_TAG=${RELEASE_TAG}" >> $GITHUB_ENV
|
||||||
|
echo "PRE_RELEASE=${PRE_RELEASE}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Check if our release tag has a correct annotation
|
||||||
|
run: |
|
||||||
|
set -ue
|
||||||
|
# Fetch all tag information as well
|
||||||
|
git fetch --prune --tags --force
|
||||||
|
|
||||||
|
echo "=========== BEGIN COMMIT MESSAGE ============="
|
||||||
|
git show ${SOURCE_TAG}
|
||||||
|
echo "============ END COMMIT MESSAGE =============="
|
||||||
|
|
||||||
|
# Quite dirty hack to get the release notes from the annotated tag
|
||||||
|
# into a temporary file.
|
||||||
|
RELEASE_NOTES=$(mktemp -p /tmp release-notes.XXXXXX)
|
||||||
|
|
||||||
|
prefix=true
|
||||||
|
begin=false
|
||||||
|
git show ${SOURCE_TAG} | while read line; do
|
||||||
|
# Whatever is in commit history for the tag, we only want that
|
||||||
|
# annotation from our tag. We discard everything else.
|
||||||
|
if test "$begin" = "false"; then
|
||||||
|
if echo "$line" | grep -q "tag ${SOURCE_TAG#refs/tags/}"; then begin="true"; fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if test "$prefix" = "true"; then
|
||||||
|
if test -z "$line"; then prefix=false; fi
|
||||||
|
else
|
||||||
|
if echo "$line" | egrep -q '^commit [0-9a-f]+'; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "$line" >> ${RELEASE_NOTES}
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# For debug purposes
|
||||||
|
echo "============BEGIN RELEASE NOTES================="
|
||||||
|
cat ${RELEASE_NOTES}
|
||||||
|
echo "=============END RELEASE NOTES=================="
|
||||||
|
|
||||||
|
# Too short release notes are suspicious. We need at least 100 bytes.
|
||||||
|
relNoteLen=$(stat -c '%s' $RELEASE_NOTES)
|
||||||
|
if test $relNoteLen -lt 100; then
|
||||||
|
echo "::error::No release notes provided in tag annotation (or tag is not annotated)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for magic string '## Quick Start' in head of release notes
|
||||||
|
if ! head -2 ${RELEASE_NOTES} | grep -iq '## Quick Start'; then
|
||||||
|
echo "::error::Release notes seem invalid, quick start section not found."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# We store path to temporary release notes file for later reading, we
|
||||||
|
# need it when creating release.
|
||||||
|
echo "RELEASE_NOTES=${RELEASE_NOTES}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Setup Golang
|
- name: Setup Golang
|
||||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GOLANG_VERSION }}
|
go-version: ${{ env.GOLANG_VERSION }}
|
||||||
|
|
||||||
|
- name: Setup Git author information
|
||||||
|
run: |
|
||||||
|
set -ue
|
||||||
|
git config --global user.email "${GIT_EMAIL}"
|
||||||
|
git config --global user.name "${GIT_USERNAME}"
|
||||||
|
|
||||||
|
- name: Checkout corresponding release branch
|
||||||
|
run: |
|
||||||
|
set -ue
|
||||||
|
echo "Switching to release branch '${TARGET_BRANCH}'"
|
||||||
|
if ! git checkout ${TARGET_BRANCH}; then
|
||||||
|
echo "::error::Checking out release branch '${TARGET_BRANCH}' for target version '${TARGET_VERSION}' (tagged '${RELEASE_TAG}') failed. Does it exist in repo?"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create VERSION information
|
||||||
|
run: |
|
||||||
|
set -ue
|
||||||
|
echo "Bumping version from $(cat VERSION) to ${TARGET_VERSION}"
|
||||||
|
echo "${TARGET_VERSION}" > VERSION
|
||||||
|
git commit -m "Bump version to ${TARGET_VERSION}" VERSION
|
||||||
|
|
||||||
|
- name: Generate new set of manifests
|
||||||
|
run: |
|
||||||
|
set -ue
|
||||||
|
make install-codegen-tools-local
|
||||||
|
make manifests-local VERSION=${TARGET_VERSION}
|
||||||
|
git diff
|
||||||
|
git commit manifests/ -m "Bump version to ${TARGET_VERSION}"
|
||||||
|
|
||||||
|
- name: Create the release tag
|
||||||
|
run: |
|
||||||
|
set -ue
|
||||||
|
echo "Creating release ${RELEASE_TAG}"
|
||||||
|
git tag ${RELEASE_TAG}
|
||||||
|
|
||||||
|
- name: Login to docker repositories
|
||||||
|
env:
|
||||||
|
DOCKER_USERNAME: ${{ secrets.RELEASE_DOCKERHUB_USERNAME }}
|
||||||
|
DOCKER_TOKEN: ${{ secrets.RELEASE_DOCKERHUB_TOKEN }}
|
||||||
|
QUAY_USERNAME: ${{ secrets.RELEASE_QUAY_USERNAME }}
|
||||||
|
QUAY_TOKEN: ${{ secrets.RELEASE_QUAY_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -ue
|
||||||
|
docker login quay.io --username "${QUAY_USERNAME}" --password-stdin <<< "${QUAY_TOKEN}"
|
||||||
|
# Remove the following when Docker Hub is gone
|
||||||
|
docker login --username "${DOCKER_USERNAME}" --password-stdin <<< "${DOCKER_TOKEN}"
|
||||||
|
if: ${{ env.DRY_RUN != 'true' }}
|
||||||
|
|
||||||
|
- uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0
|
||||||
|
- uses: docker/setup-buildx-action@15c905b16b06416d2086efa066dd8e3a35cc7f98 # v2.4.0
|
||||||
|
- name: Build and push Docker image for release
|
||||||
|
run: |
|
||||||
|
set -ue
|
||||||
|
git clean -fd
|
||||||
|
mkdir -p dist/
|
||||||
|
docker buildx build --platform linux/amd64,linux/arm64,linux/s390x,linux/ppc64le --sbom=false --provenance=false --push -t ${IMAGE_NAMESPACE}/argocd:v${TARGET_VERSION} -t argoproj/argocd:v${TARGET_VERSION} .
|
||||||
|
make release-cli
|
||||||
|
make checksums
|
||||||
|
chmod +x ./dist/argocd-linux-amd64
|
||||||
|
./dist/argocd-linux-amd64 version --client
|
||||||
|
if: ${{ env.DRY_RUN != 'true' }}
|
||||||
|
|
||||||
|
- name: Install cosign
|
||||||
|
uses: sigstore/cosign-installer@9becc617647dfa20ae7b1151972e9b3a2c338a2b # v2.8.1
|
||||||
|
with:
|
||||||
|
cosign-release: 'v1.13.1'
|
||||||
|
|
||||||
|
- name: Install crane to get digest of image
|
||||||
|
uses: imjasonh/setup-crane@e82f1b9a8007d399333baba4d75915558e9fb6a4
|
||||||
|
|
||||||
|
- name: Get digest of image
|
||||||
|
run: |
|
||||||
|
echo "IMAGE_DIGEST=$(crane digest quay.io/argoproj/argocd:v${TARGET_VERSION})" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Sign Argo CD container images and assets
|
||||||
|
run: |
|
||||||
|
cosign sign --key env://COSIGN_PRIVATE_KEY ${IMAGE_NAMESPACE}/argocd@${{ env.IMAGE_DIGEST }}
|
||||||
|
cosign sign-blob --key env://COSIGN_PRIVATE_KEY ./dist/argocd-${TARGET_VERSION}-checksums.txt > ./dist/argocd-${TARGET_VERSION}-checksums.sig
|
||||||
|
# Retrieves the public key to release as an asset
|
||||||
|
cosign public-key --key env://COSIGN_PRIVATE_KEY > ./dist/argocd-cosign.pub
|
||||||
|
env:
|
||||||
|
COSIGN_PRIVATE_KEY: ${{secrets.COSIGN_PRIVATE_KEY}}
|
||||||
|
COSIGN_PASSWORD: ${{secrets.COSIGN_PASSWORD}}
|
||||||
|
if: ${{ env.DRY_RUN != 'true' }}
|
||||||
|
|
||||||
|
- name: Read release notes file
|
||||||
|
id: release-notes
|
||||||
|
uses: juliangruber/read-file-action@02bbba9876a8f870efd4ad64e3b9088d3fb94d4b # v1.1.6
|
||||||
|
with:
|
||||||
|
path: ${{ env.RELEASE_NOTES }}
|
||||||
|
|
||||||
|
- name: Push changes to release branch
|
||||||
|
run: |
|
||||||
|
set -ue
|
||||||
|
git push origin ${TARGET_BRANCH}
|
||||||
|
git push origin ${RELEASE_TAG}
|
||||||
|
|
||||||
|
- name: Dry run GitHub release
|
||||||
|
uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e # v1.1.4
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
id: create_release
|
||||||
|
with:
|
||||||
|
tag_name: ${{ env.RELEASE_TAG }}
|
||||||
|
release_name: ${{ env.RELEASE_TAG }}
|
||||||
|
draft: ${{ env.DRAFT_RELEASE }}
|
||||||
|
prerelease: ${{ env.PRE_RELEASE }}
|
||||||
|
body: ${{ steps.release-notes.outputs.content }}
|
||||||
|
if: ${{ env.DRY_RUN == 'true' }}
|
||||||
|
|
||||||
- name: Generate SBOM (spdx)
|
- name: Generate SBOM (spdx)
|
||||||
id: spdx-builder
|
id: spdx-builder
|
||||||
env:
|
env:
|
||||||
@@ -166,7 +273,7 @@ jobs:
|
|||||||
# managers (gomod, yarn, npm).
|
# managers (gomod, yarn, npm).
|
||||||
PROJECT_FOLDERS: ".,./ui"
|
PROJECT_FOLDERS: ".,./ui"
|
||||||
# full qualified name of the docker image to be inspected
|
# full qualified name of the docker image to be inspected
|
||||||
DOCKER_IMAGE: quay.io/argoproj/argocd:${{ github.ref_name }}
|
DOCKER_IMAGE: ${{env.IMAGE_NAMESPACE}}/argocd:v${{env.TARGET_VERSION}}
|
||||||
run: |
|
run: |
|
||||||
yarn install --cwd ./ui
|
yarn install --cwd ./ui
|
||||||
go install github.com/spdx/spdx-sbom-generator/cmd/generator@$SPDX_GEN_VERSION
|
go install github.com/spdx/spdx-sbom-generator/cmd/generator@$SPDX_GEN_VERSION
|
||||||
@@ -184,122 +291,43 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
cd /tmp && tar -zcf sbom.tar.gz *.spdx
|
cd /tmp && tar -zcf sbom.tar.gz *.spdx
|
||||||
|
if: ${{ env.DRY_RUN != 'true' }}
|
||||||
|
|
||||||
- name: Generate SBOM hash
|
- name: Sign sbom
|
||||||
shell: bash
|
|
||||||
id: sbom-hash
|
|
||||||
run: |
|
run: |
|
||||||
# sha256sum generates sha256 hash for sbom.
|
cosign sign-blob --key env://COSIGN_PRIVATE_KEY /tmp/sbom.tar.gz > /tmp/sbom.tar.gz.sig
|
||||||
# base64 -w0 encodes to base64 and outputs on a single line.
|
env:
|
||||||
# sha256sum /tmp/sbom.tar.gz ... | base64 -w0
|
COSIGN_PRIVATE_KEY: ${{secrets.COSIGN_PRIVATE_KEY}}
|
||||||
echo "hashes=$(sha256sum /tmp/sbom.tar.gz | base64 -w0)" >> "$GITHUB_OUTPUT"
|
COSIGN_PASSWORD: ${{secrets.COSIGN_PASSWORD}}
|
||||||
|
if: ${{ env.DRY_RUN != 'true' }}
|
||||||
|
|
||||||
- name: Upload SBOM
|
- name: Create GitHub release
|
||||||
uses: softprops/action-gh-release@7b4da11513bf3f43f9999e90eabced41ab8bb048 # v2.2.0
|
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
|
name: ${{ env.RELEASE_TAG }}
|
||||||
|
tag_name: ${{ env.RELEASE_TAG }}
|
||||||
|
draft: ${{ env.DRAFT_RELEASE }}
|
||||||
|
prerelease: ${{ env.PRE_RELEASE }}
|
||||||
|
body: ${{ steps.release-notes.outputs.content }} # Pre-pended to the generated notes
|
||||||
files: |
|
files: |
|
||||||
|
dist/argocd-*
|
||||||
/tmp/sbom.tar.gz
|
/tmp/sbom.tar.gz
|
||||||
|
/tmp/sbom.tar.gz.sig
|
||||||
|
if: ${{ env.DRY_RUN != 'true' }}
|
||||||
|
|
||||||
sbom-provenance:
|
- name: Update homebrew formula
|
||||||
needs: [generate-sbom]
|
env:
|
||||||
permissions:
|
HOMEBREW_TOKEN: ${{ secrets.RELEASE_HOMEBREW_TOKEN }}
|
||||||
actions: read # for detecting the Github Actions environment
|
uses: dawidd6/action-homebrew-bump-formula@02e79d9da43d79efa846d73695b6052cbbdbf48a # v3.8.3
|
||||||
id-token: write # Needed for provenance signing and ID
|
|
||||||
contents: write # Needed for release uploads
|
|
||||||
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
|
|
||||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
|
|
||||||
with:
|
|
||||||
base64-subjects: "${{ needs.generate-sbom.outputs.hashes }}"
|
|
||||||
provenance-name: "argocd-sbom.intoto.jsonl"
|
|
||||||
upload-assets: true
|
|
||||||
|
|
||||||
post-release:
|
|
||||||
needs:
|
|
||||||
- argocd-image
|
|
||||||
- goreleaser
|
|
||||||
- generate-sbom
|
|
||||||
permissions:
|
|
||||||
contents: write # Needed to push commit to update stable tag
|
|
||||||
pull-requests: write # Needed to create PR for VERSION update.
|
|
||||||
if: github.repository == 'argoproj/argo-cd'
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
token: ${{env.HOMEBREW_TOKEN}}
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
formula: argocd
|
||||||
|
if: ${{ env.HOMEBREW_TOKEN != '' && env.UPDATE_HOMEBREW == 'true' && env.PRE_RELEASE != 'true' }}
|
||||||
|
|
||||||
- name: Setup Git author information
|
- name: Delete original request tag from repository
|
||||||
run: |
|
run: |
|
||||||
set -ue
|
set -ue
|
||||||
git config --global user.email 'ci@argoproj.com'
|
git push --delete origin ${SOURCE_TAG}
|
||||||
git config --global user.name 'CI'
|
if: ${{ always() }}
|
||||||
|
|
||||||
- name: Check if tag is the latest version and not a pre-release
|
|
||||||
run: |
|
|
||||||
set -xue
|
|
||||||
# Fetch all tag information
|
|
||||||
git fetch --prune --tags --force
|
|
||||||
|
|
||||||
LATEST_TAG=$(git -c 'versionsort.suffix=-rc' tag --list --sort=version:refname | tail -n1)
|
|
||||||
|
|
||||||
PRE_RELEASE=false
|
|
||||||
# Check if latest tag is a pre-release
|
|
||||||
if echo $LATEST_TAG | grep -E -- '-rc[0-9]+$';then
|
|
||||||
PRE_RELEASE=true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ensure latest tag matches github.ref_name & not a pre-release
|
|
||||||
if [[ $LATEST_TAG == ${{ github.ref_name }} ]] && [[ $PRE_RELEASE != 'true' ]];then
|
|
||||||
echo "TAG_STABLE=true" >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo "TAG_STABLE=false" >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Update stable tag to latest version
|
|
||||||
run: |
|
|
||||||
git tag -f stable ${{ github.ref_name }}
|
|
||||||
git push -f origin stable
|
|
||||||
if: ${{ env.TAG_STABLE == 'true' }}
|
|
||||||
|
|
||||||
- name: Check to see if VERSION should be updated on master branch
|
|
||||||
run: |
|
|
||||||
set -xue
|
|
||||||
SOURCE_TAG=${{ github.ref_name }}
|
|
||||||
VERSION_REF="${SOURCE_TAG#*v}"
|
|
||||||
COMMIT_HASH=$(git rev-parse HEAD)
|
|
||||||
if echo "$VERSION_REF" | grep -E -- '^[0-9]+\.[0-9]+\.0-rc1';then
|
|
||||||
VERSION=$(awk 'BEGIN {FS=OFS="."} {$2++; print}' <<< "${VERSION_REF%-rc1}")
|
|
||||||
echo "Updating VERSION to: $VERSION"
|
|
||||||
echo "UPDATE_VERSION=true" >> $GITHUB_ENV
|
|
||||||
echo "NEW_VERSION=$VERSION" >> $GITHUB_ENV
|
|
||||||
echo "COMMIT_HASH=$COMMIT_HASH" >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo "Not updating VERSION"
|
|
||||||
echo "UPDATE_VERSION=false" >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Update VERSION on master branch
|
|
||||||
run: |
|
|
||||||
echo ${{ env.NEW_VERSION }} > VERSION
|
|
||||||
# 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
|
|
||||||
# Update the 'commit-hash: XXXXXXX' line in SECURITY-INSIGHTS.yml
|
|
||||||
sed -i "s/commit-hash: .*/commit-hash: ${{ env.COMMIT_HASH }}/" SECURITY-INSIGHTS.yml
|
|
||||||
if: ${{ env.UPDATE_VERSION == 'true' }}
|
|
||||||
|
|
||||||
- name: Create PR to update VERSION on master branch
|
|
||||||
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
|
|
||||||
with:
|
|
||||||
commit-message: Bump version in master
|
|
||||||
title: "chore: Bump version in master"
|
|
||||||
body: All images built from master should indicate which version we are on track for.
|
|
||||||
signoff: true
|
|
||||||
branch: update-version
|
|
||||||
branch-suffix: random
|
|
||||||
base: master
|
|
||||||
if: ${{ env.UPDATE_VERSION == 'true' }}
|
|
||||||
|
|||||||
67
.github/workflows/scorecard.yaml
vendored
67
.github/workflows/scorecard.yaml
vendored
@@ -1,67 +0,0 @@
|
|||||||
name: Scorecards supply-chain security
|
|
||||||
on:
|
|
||||||
# Only the default branch is supported.
|
|
||||||
branch_protection_rule:
|
|
||||||
schedule:
|
|
||||||
- cron: "39 9 * * 2"
|
|
||||||
push:
|
|
||||||
branches: ["master"]
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
# Declare default permissions as read only.
|
|
||||||
permissions: read-all
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analysis:
|
|
||||||
name: Scorecards analysis
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
permissions:
|
|
||||||
# Needed to upload the results to code-scanning dashboard.
|
|
||||||
security-events: write
|
|
||||||
# Used to receive a badge. (Upcoming feature)
|
|
||||||
id-token: write
|
|
||||||
# Needs for private repositories.
|
|
||||||
contents: read
|
|
||||||
actions: read
|
|
||||||
if: github.repository == 'argoproj/argo-cd'
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: "Checkout code"
|
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: "Run analysis"
|
|
||||||
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
|
|
||||||
with:
|
|
||||||
results_file: results.sarif
|
|
||||||
results_format: sarif
|
|
||||||
# (Optional) Read-only PAT token. Uncomment the `repo_token` line below if:
|
|
||||||
# - you want to enable the Branch-Protection check on a *public* repository, or
|
|
||||||
# - you are installing Scorecards on a *private* repository
|
|
||||||
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
|
|
||||||
# repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}
|
|
||||||
|
|
||||||
# Publish the results for public repositories to enable scorecard badges. For more details, see
|
|
||||||
# https://github.com/ossf/scorecard-action#publishing-results.
|
|
||||||
# For private repositories, `publish_results` will automatically be set to `false`, regardless
|
|
||||||
# of the value entered here.
|
|
||||||
publish_results: true
|
|
||||||
|
|
||||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
|
||||||
# format to the repository Actions tab.
|
|
||||||
- name: "Upload artifact"
|
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
|
||||||
with:
|
|
||||||
name: SARIF file
|
|
||||||
path: results.sarif
|
|
||||||
retention-days: 5
|
|
||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
|
||||||
- name: "Upload to code-scanning"
|
|
||||||
uses: github/codeql-action/upload-sarif@8fcfedf57053e09257688fce7a0beeb18b1b9ae3 # v2.17.2
|
|
||||||
with:
|
|
||||||
sarif_file: results.sarif
|
|
||||||
2
.github/workflows/update-snyk.yaml
vendored
2
.github/workflows/update-snyk.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build reports
|
- name: Build reports
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,7 +1,6 @@
|
|||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.run/
|
|
||||||
vendor/
|
vendor/
|
||||||
dist/*
|
dist/*
|
||||||
ui/dist/app/*
|
ui/dist/app/*
|
||||||
@@ -19,8 +18,6 @@ node_modules/
|
|||||||
.kube/
|
.kube/
|
||||||
./test/cmp/*.sock
|
./test/cmp/*.sock
|
||||||
.envrc.remote
|
.envrc.remote
|
||||||
.*.swp
|
|
||||||
rerunreport.txt
|
|
||||||
|
|
||||||
# ignore built binaries
|
# ignore built binaries
|
||||||
cmd/argocd/argocd
|
cmd/argocd/argocd
|
||||||
|
|||||||
4
.gitpod.Dockerfile
vendored
4
.gitpod.Dockerfile
vendored
@@ -1,4 +1,4 @@
|
|||||||
FROM gitpod/workspace-full@sha256:230285e0b949e6d728d384b2029a4111db7b9c87c182f22f32a0be9e36b225df
|
FROM gitpod/workspace-full
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
@@ -13,8 +13,6 @@ ENV GOCACHE=/go-build-cache
|
|||||||
RUN apt-get install redis-server -y
|
RUN apt-get install redis-server -y
|
||||||
RUN go install github.com/mattn/goreman@latest
|
RUN go install github.com/mattn/goreman@latest
|
||||||
|
|
||||||
RUN chown -R gitpod:gitpod /go-build-cache
|
|
||||||
|
|
||||||
USER gitpod
|
USER gitpod
|
||||||
|
|
||||||
ENV ARGOCD_REDIS_LOCAL=true
|
ENV ARGOCD_REDIS_LOCAL=true
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
issues:
|
|
||||||
exclude:
|
|
||||||
- SA5011
|
|
||||||
max-issues-per-linter: 0
|
|
||||||
max-same-issues: 0
|
|
||||||
exclude-rules:
|
|
||||||
- path: '(.+)_test\.go'
|
|
||||||
linters:
|
|
||||||
- unparam
|
|
||||||
linters:
|
|
||||||
enable:
|
|
||||||
- errcheck
|
|
||||||
- errorlint
|
|
||||||
- gocritic
|
|
||||||
- gofumpt
|
|
||||||
- goimports
|
|
||||||
- gosimple
|
|
||||||
- govet
|
|
||||||
- ineffassign
|
|
||||||
- misspell
|
|
||||||
- perfsprint
|
|
||||||
- staticcheck
|
|
||||||
- testifylint
|
|
||||||
- thelper
|
|
||||||
- unparam
|
|
||||||
- unused
|
|
||||||
- usestdlibvars
|
|
||||||
- whitespace
|
|
||||||
linters-settings:
|
|
||||||
gocritic:
|
|
||||||
disabled-checks:
|
|
||||||
- appendAssign
|
|
||||||
- assignOp # Keep it disabled for readability
|
|
||||||
- badCond
|
|
||||||
- commentFormatting
|
|
||||||
- exitAfterDefer
|
|
||||||
- ifElseChain
|
|
||||||
- mapKey
|
|
||||||
- singleCaseSwitch
|
|
||||||
- typeSwitchVar
|
|
||||||
goimports:
|
|
||||||
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:
|
|
||||||
enable-all: true
|
|
||||||
disable:
|
|
||||||
- go-require
|
|
||||||
run:
|
|
||||||
timeout: 50m
|
|
||||||
123
.goreleaser.yaml
123
.goreleaser.yaml
@@ -1,123 +0,0 @@
|
|||||||
version: 2
|
|
||||||
|
|
||||||
project_name: argocd
|
|
||||||
|
|
||||||
before:
|
|
||||||
hooks:
|
|
||||||
- go mod download
|
|
||||||
- make build-ui
|
|
||||||
|
|
||||||
builds:
|
|
||||||
- id: argocd-cli
|
|
||||||
main: ./cmd
|
|
||||||
binary: argocd-{{ .Os}}-{{ .Arch}}
|
|
||||||
env:
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
flags:
|
|
||||||
- -v
|
|
||||||
ldflags:
|
|
||||||
- -X github.com/argoproj/argo-cd/v2/common.version={{ .Version }}
|
|
||||||
- -X github.com/argoproj/argo-cd/v2/common.buildDate={{ .Date }}
|
|
||||||
- -X github.com/argoproj/argo-cd/v2/common.gitCommit={{ .FullCommit }}
|
|
||||||
- -X github.com/argoproj/argo-cd/v2/common.gitTreeState={{ .Env.GIT_TREE_STATE }}
|
|
||||||
- -X github.com/argoproj/argo-cd/v2/common.kubectlVersion={{ .Env.KUBECTL_VERSION }}
|
|
||||||
- -extldflags="-static"
|
|
||||||
goos:
|
|
||||||
- linux
|
|
||||||
- darwin
|
|
||||||
- windows
|
|
||||||
goarch:
|
|
||||||
- amd64
|
|
||||||
- arm64
|
|
||||||
- s390x
|
|
||||||
- ppc64le
|
|
||||||
ignore:
|
|
||||||
- goos: darwin
|
|
||||||
goarch: s390x
|
|
||||||
- goos: darwin
|
|
||||||
goarch: ppc64le
|
|
||||||
- goos: windows
|
|
||||||
goarch: s390x
|
|
||||||
- goos: windows
|
|
||||||
goarch: ppc64le
|
|
||||||
- goos: windows
|
|
||||||
goarch: arm64
|
|
||||||
|
|
||||||
archives:
|
|
||||||
- id: argocd-archive
|
|
||||||
builds:
|
|
||||||
- argocd-cli
|
|
||||||
name_template: |-
|
|
||||||
{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}
|
|
||||||
format: binary
|
|
||||||
|
|
||||||
checksum:
|
|
||||||
name_template: 'cli_checksums.txt'
|
|
||||||
algorithm: sha256
|
|
||||||
|
|
||||||
release:
|
|
||||||
prerelease: auto
|
|
||||||
draft: false
|
|
||||||
header: |
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Non-HA:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
kubectl create namespace argocd
|
|
||||||
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/{{.Tag}}/manifests/install.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
### HA:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
kubectl create namespace argocd
|
|
||||||
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/{{.Tag}}/manifests/ha/install.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
## Release Signatures and Provenance
|
|
||||||
|
|
||||||
All Argo CD container images are signed by cosign. A Provenance is generated for container images and CLI binaries which meet the SLSA Level 3 specifications. See the [documentation](https://argo-cd.readthedocs.io/en/stable/operator-manual/signed-release-assets) on how to verify.
|
|
||||||
|
|
||||||
|
|
||||||
## Upgrading
|
|
||||||
|
|
||||||
If upgrading from a different minor version, be sure to read the [upgrading](https://argo-cd.readthedocs.io/en/stable/operator-manual/upgrading/overview/) documentation.
|
|
||||||
footer: |
|
|
||||||
**Full Changelog**: https://github.com/argoproj/argo-cd/compare/{{ .PreviousTag }}...{{ .Tag }}
|
|
||||||
|
|
||||||
<a href="https://argoproj.github.io/cd/"><img src="https://raw.githubusercontent.com/argoproj/argo-site/master/content/pages/cd/gitops-cd.png" width="25%" ></a>
|
|
||||||
|
|
||||||
|
|
||||||
snapshot: #### To be removed for PR
|
|
||||||
name_template: "2.6.0"
|
|
||||||
|
|
||||||
changelog:
|
|
||||||
use:
|
|
||||||
github
|
|
||||||
sort: asc
|
|
||||||
abbrev: 0
|
|
||||||
groups: # Regex use RE2 syntax as defined here: https://github.com/google/re2/wiki/Syntax.
|
|
||||||
- title: 'Features'
|
|
||||||
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
|
|
||||||
order: 100
|
|
||||||
- title: 'Bug fixes'
|
|
||||||
regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
|
|
||||||
order: 200
|
|
||||||
- title: 'Documentation'
|
|
||||||
regexp: '^.*?docs(\([[:word:]]+\))??!?:.+$'
|
|
||||||
order: 300
|
|
||||||
- title: 'Dependency updates'
|
|
||||||
regexp: '^.*?(feat|fix|chore)\(deps?.+\)!?:.+$'
|
|
||||||
order: 400
|
|
||||||
- title: 'Other work'
|
|
||||||
order: 999
|
|
||||||
filters:
|
|
||||||
exclude:
|
|
||||||
- '^test:'
|
|
||||||
- '^.*?Bump(\([[:word:]]+\))?.+$'
|
|
||||||
- '^.*?\[Bot\](\([[:word:]]+\))?.+$'
|
|
||||||
|
|
||||||
|
|
||||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
|
||||||
|
|
||||||
@@ -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:
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
version: 2
|
|
||||||
formats: all
|
|
||||||
mkdocs:
|
|
||||||
fail_on_warning: false
|
|
||||||
python:
|
|
||||||
install:
|
|
||||||
- requirements: docs/requirements.txt
|
|
||||||
build:
|
|
||||||
os: "ubuntu-22.04"
|
|
||||||
tools:
|
|
||||||
python: "3.12"
|
|
||||||
7
.readthedocs.yml
Normal file
7
.readthedocs.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
version: 2
|
||||||
|
formats: all
|
||||||
|
mkdocs:
|
||||||
|
fail_on_warning: false
|
||||||
|
python:
|
||||||
|
install:
|
||||||
|
- requirements: docs/requirements.txt
|
||||||
14
CODEOWNERS
14
CODEOWNERS
@@ -1,14 +0,0 @@
|
|||||||
# All
|
|
||||||
** @argoproj/argocd-approvers
|
|
||||||
|
|
||||||
# Docs
|
|
||||||
/docs/** @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
|
|
||||||
/USERS.md @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
|
|
||||||
/README.md @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
|
|
||||||
/mkdocs.yml @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
|
|
||||||
|
|
||||||
# CI
|
|
||||||
/.codecov.yml @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
|
|
||||||
/.github/** @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
|
|
||||||
/.goreleaser.yaml @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
|
|
||||||
/sonar-project.properties @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Please refer to [the Contribution Guide](https://argo-cd.readthedocs.io/en/latest/developer-guide/code-contributions/)
|
|
||||||
31
Dockerfile
31
Dockerfile
@@ -1,12 +1,12 @@
|
|||||||
ARG BASE_IMAGE=docker.io/library/ubuntu:24.04@sha256:3f85b7caad41a95462cf5b787d8a04604c8262cdcdf9a472b8c52ef83375fe15
|
ARG BASE_IMAGE=docker.io/library/ubuntu:22.04
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
# Builder image
|
# Builder image
|
||||||
# Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image
|
# Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image
|
||||||
# Also used as the image in CI jobs so needs all dependencies
|
# Also used as the image in CI jobs so needs all dependencies
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
FROM docker.io/library/golang:1.23.3@sha256:d56c3e08fe5b27729ee3834854ae8f7015af48fd651cd25d1e3bcf3c19830174 AS builder
|
FROM docker.io/library/golang:1.18 AS builder
|
||||||
|
|
||||||
RUN echo 'deb http://archive.debian.org/debian buster-backports main' >> /etc/apt/sources.list
|
RUN echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list
|
||||||
|
|
||||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||||
openssh-server \
|
openssh-server \
|
||||||
@@ -28,7 +28,7 @@ WORKDIR /tmp
|
|||||||
COPY hack/install.sh hack/tool-versions.sh ./
|
COPY hack/install.sh hack/tool-versions.sh ./
|
||||||
COPY hack/installers installers
|
COPY hack/installers installers
|
||||||
|
|
||||||
RUN ./install.sh helm && \
|
RUN ./install.sh helm-linux && \
|
||||||
INSTALL_PATH=/usr/local/bin ./install.sh kustomize
|
INSTALL_PATH=/usr/local/bin ./install.sh kustomize
|
||||||
|
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
@@ -51,7 +51,7 @@ RUN groupadd -g $ARGOCD_USER_ID argocd && \
|
|||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get dist-upgrade -y && \
|
apt-get dist-upgrade -y && \
|
||||||
apt-get install -y \
|
apt-get install -y \
|
||||||
git git-lfs tini gpg tzdata connect-proxy && \
|
git git-lfs tini gpg tzdata && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
@@ -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:12.18.4 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.18 AS argocd-build
|
||||||
|
|
||||||
WORKDIR /go/src/github.com/argoproj/argo-cd
|
WORKDIR /go/src/github.com/argoproj/argo-cd
|
||||||
|
|
||||||
@@ -113,18 +113,7 @@ COPY . .
|
|||||||
COPY --from=argocd-ui /src/dist/app /go/src/github.com/argoproj/argo-cd/ui/dist/app
|
COPY --from=argocd-ui /src/dist/app /go/src/github.com/argoproj/argo-cd/ui/dist/app
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
# These build args are optional; if not specified the defaults will be taken from the Makefile
|
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH make argocd-all
|
||||||
ARG GIT_TAG
|
|
||||||
ARG BUILD_DATE
|
|
||||||
ARG GIT_TREE_STATE
|
|
||||||
ARG GIT_COMMIT
|
|
||||||
RUN GIT_COMMIT=$GIT_COMMIT \
|
|
||||||
GIT_TREE_STATE=$GIT_TREE_STATE \
|
|
||||||
GIT_TAG=$GIT_TAG \
|
|
||||||
BUILD_DATE=$BUILD_DATE \
|
|
||||||
GOOS=$TARGETOS \
|
|
||||||
GOARCH=$TARGETARCH \
|
|
||||||
make argocd-all
|
|
||||||
|
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
# Final image
|
# Final image
|
||||||
@@ -140,8 +129,6 @@ 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", "--"]
|
|
||||||
|
|||||||
212
Makefile
212
Makefile
@@ -3,41 +3,31 @@ CURRENT_DIR=$(shell pwd)
|
|||||||
DIST_DIR=${CURRENT_DIR}/dist
|
DIST_DIR=${CURRENT_DIR}/dist
|
||||||
CLI_NAME=argocd
|
CLI_NAME=argocd
|
||||||
BIN_NAME=argocd
|
BIN_NAME=argocd
|
||||||
CGO_FLAG=0
|
|
||||||
|
|
||||||
GEN_RESOURCES_CLI_NAME=argocd-resources-gen
|
GEN_RESOURCES_CLI_NAME=argocd-resources-gen
|
||||||
|
|
||||||
HOST_OS:=$(shell go env GOOS)
|
HOST_OS:=$(shell go env GOOS)
|
||||||
HOST_ARCH:=$(shell go env GOARCH)
|
HOST_ARCH:=$(shell go env GOARCH)
|
||||||
|
|
||||||
TARGET_ARCH?=linux/amd64
|
|
||||||
|
|
||||||
VERSION=$(shell cat ${CURRENT_DIR}/VERSION)
|
VERSION=$(shell cat ${CURRENT_DIR}/VERSION)
|
||||||
BUILD_DATE:=$(if $(BUILD_DATE),$(BUILD_DATE),$(shell date -u +'%Y-%m-%dT%H:%M:%SZ'))
|
BUILD_DATE=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||||
GIT_COMMIT:=$(if $(GIT_COMMIT),$(GIT_COMMIT),$(shell git rev-parse HEAD))
|
GIT_COMMIT=$(shell git rev-parse HEAD)
|
||||||
GIT_TAG:=$(if $(GIT_TAG),$(GIT_TAG),$(shell if [ -z "`git status --porcelain`" ]; then git describe --exact-match --tags HEAD 2>/dev/null; fi))
|
GIT_TAG=$(shell if [ -z "`git status --porcelain`" ]; then git describe --exact-match --tags HEAD 2>/dev/null; fi)
|
||||||
GIT_TREE_STATE:=$(if $(GIT_TREE_STATE),$(GIT_TREE_STATE),$(shell if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi))
|
GIT_TREE_STATE=$(shell if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)
|
||||||
VOLUME_MOUNT=$(shell if test "$(go env GOOS)" = "darwin"; then echo ":delegated"; elif test selinuxenabled; then echo ":delegated"; else echo ""; fi)
|
VOLUME_MOUNT=$(shell if test "$(go env GOOS)" = "darwin"; then echo ":delegated"; elif test selinuxenabled; then echo ":delegated"; else echo ""; fi)
|
||||||
KUBECTL_VERSION=$(shell go list -m k8s.io/client-go | head -n 1 | rev | cut -d' ' -f1 | rev)
|
KUBECTL_VERSION=$(shell go list -m k8s.io/client-go | head -n 1 | rev | cut -d' ' -f1 | rev)
|
||||||
|
|
||||||
GOPATH?=$(shell if test -x `which go`; then go env GOPATH; else echo "$(HOME)/go"; fi)
|
GOPATH?=$(shell if test -x `which go`; then go env GOPATH; else echo "$(HOME)/go"; fi)
|
||||||
GOCACHE?=$(HOME)/.cache/go-build
|
GOCACHE?=$(HOME)/.cache/go-build
|
||||||
|
|
||||||
# Docker command to use
|
|
||||||
DOCKER?=docker
|
|
||||||
ifeq ($(DOCKER),podman)
|
|
||||||
PODMAN_ARGS=--userns keep-id
|
|
||||||
else
|
|
||||||
PODMAN_ARGS=
|
|
||||||
endif
|
|
||||||
|
|
||||||
DOCKER_SRCDIR?=$(GOPATH)/src
|
DOCKER_SRCDIR?=$(GOPATH)/src
|
||||||
DOCKER_WORKDIR?=/go/src/github.com/argoproj/argo-cd
|
DOCKER_WORKDIR?=/go/src/github.com/argoproj/argo-cd
|
||||||
|
|
||||||
ARGOCD_PROCFILE?=Procfile
|
ARGOCD_PROCFILE?=Procfile
|
||||||
|
|
||||||
# pointing to python 3.7 to match https://github.com/argoproj/argo-cd/blob/master/.readthedocs.yml
|
# Strict mode has been disabled in latest versions of mkdocs-material.
|
||||||
MKDOCS_DOCKER_IMAGE?=python:3.7-alpine
|
# Thus pointing to the older image of mkdocs-material matching the version used by argo-cd.
|
||||||
|
MKDOCS_DOCKER_IMAGE?=squidfunk/mkdocs-material:4.1.1
|
||||||
MKDOCS_RUN_ARGS?=
|
MKDOCS_RUN_ARGS?=
|
||||||
|
|
||||||
# Configuration for building argocd-test-tools image
|
# Configuration for building argocd-test-tools image
|
||||||
@@ -57,7 +47,7 @@ ARGOCD_E2E_DEX_PORT?=5556
|
|||||||
ARGOCD_E2E_YARN_HOST?=localhost
|
ARGOCD_E2E_YARN_HOST?=localhost
|
||||||
ARGOCD_E2E_DISABLE_AUTH?=
|
ARGOCD_E2E_DISABLE_AUTH?=
|
||||||
|
|
||||||
ARGOCD_E2E_TEST_TIMEOUT?=90m
|
ARGOCD_E2E_TEST_TIMEOUT?=45m
|
||||||
|
|
||||||
ARGOCD_IN_CI?=false
|
ARGOCD_IN_CI?=false
|
||||||
ARGOCD_TEST_E2E?=true
|
ARGOCD_TEST_E2E?=true
|
||||||
@@ -84,7 +74,7 @@ SUDO?=
|
|||||||
# Runs any command in the argocd-test-utils container in server mode
|
# Runs any command in the argocd-test-utils container in server mode
|
||||||
# Server mode container will start with uid 0 and drop privileges during runtime
|
# Server mode container will start with uid 0 and drop privileges during runtime
|
||||||
define run-in-test-server
|
define run-in-test-server
|
||||||
$(SUDO) $(DOCKER) run --rm -it \
|
$(SUDO) docker run --rm -it \
|
||||||
--name argocd-test-server \
|
--name argocd-test-server \
|
||||||
-u $(CONTAINER_UID):$(CONTAINER_GID) \
|
-u $(CONTAINER_UID):$(CONTAINER_GID) \
|
||||||
-e USER_ID=$(CONTAINER_UID) \
|
-e USER_ID=$(CONTAINER_UID) \
|
||||||
@@ -109,14 +99,13 @@ define run-in-test-server
|
|||||||
-p ${ARGOCD_E2E_APISERVER_PORT}:8080 \
|
-p ${ARGOCD_E2E_APISERVER_PORT}:8080 \
|
||||||
-p 4000:4000 \
|
-p 4000:4000 \
|
||||||
-p 5000:5000 \
|
-p 5000:5000 \
|
||||||
$(PODMAN_ARGS) \
|
|
||||||
$(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \
|
$(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \
|
||||||
bash -c "$(1)"
|
bash -c "$(1)"
|
||||||
endef
|
endef
|
||||||
|
|
||||||
# Runs any command in the argocd-test-utils container in client mode
|
# Runs any command in the argocd-test-utils container in client mode
|
||||||
define run-in-test-client
|
define run-in-test-client
|
||||||
$(SUDO) $(DOCKER) run --rm -it \
|
$(SUDO) docker run --rm -it \
|
||||||
--name argocd-test-client \
|
--name argocd-test-client \
|
||||||
-u $(CONTAINER_UID):$(CONTAINER_GID) \
|
-u $(CONTAINER_UID):$(CONTAINER_GID) \
|
||||||
-e HOME=/home/user \
|
-e HOME=/home/user \
|
||||||
@@ -131,14 +120,13 @@ define run-in-test-client
|
|||||||
-v ${HOME}/.kube:/home/user/.kube${VOLUME_MOUNT} \
|
-v ${HOME}/.kube:/home/user/.kube${VOLUME_MOUNT} \
|
||||||
-v /tmp:/tmp${VOLUME_MOUNT} \
|
-v /tmp:/tmp${VOLUME_MOUNT} \
|
||||||
-w ${DOCKER_WORKDIR} \
|
-w ${DOCKER_WORKDIR} \
|
||||||
$(PODMAN_ARGS) \
|
|
||||||
$(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \
|
$(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \
|
||||||
bash -c "$(1)"
|
bash -c "$(1)"
|
||||||
endef
|
endef
|
||||||
|
|
||||||
#
|
#
|
||||||
define exec-in-test-server
|
define exec-in-test-server
|
||||||
$(SUDO) $(DOCKER) exec -it -u $(CONTAINER_UID):$(CONTAINER_GID) -e ARGOCD_E2E_RECORD=$(ARGOCD_E2E_RECORD) -e ARGOCD_E2E_K3S=$(ARGOCD_E2E_K3S) argocd-test-server $(1)
|
$(SUDO) docker exec -it -u $(CONTAINER_UID):$(CONTAINER_GID) -e ARGOCD_E2E_RECORD=$(ARGOCD_E2E_RECORD) -e ARGOCD_E2E_K3S=$(ARGOCD_E2E_K3S) argocd-test-server $(1)
|
||||||
endef
|
endef
|
||||||
|
|
||||||
PATH:=$(PATH):$(PWD)/hack
|
PATH:=$(PATH):$(PWD)/hack
|
||||||
@@ -153,20 +141,12 @@ DEV_IMAGE?=false
|
|||||||
ARGOCD_GPG_ENABLED?=true
|
ARGOCD_GPG_ENABLED?=true
|
||||||
ARGOCD_E2E_APISERVER_PORT?=8080
|
ARGOCD_E2E_APISERVER_PORT?=8080
|
||||||
|
|
||||||
ifeq (${COVERAGE_ENABLED}, true)
|
|
||||||
# We use this in the cli-local target to enable code coverage for e2e tests.
|
|
||||||
COVERAGE_FLAG=-cover
|
|
||||||
else
|
|
||||||
COVERAGE_FLAG=
|
|
||||||
endif
|
|
||||||
|
|
||||||
override LDFLAGS += \
|
override LDFLAGS += \
|
||||||
-X ${PACKAGE}.version=${VERSION} \
|
-X ${PACKAGE}.version=${VERSION} \
|
||||||
-X ${PACKAGE}.buildDate=${BUILD_DATE} \
|
-X ${PACKAGE}.buildDate=${BUILD_DATE} \
|
||||||
-X ${PACKAGE}.gitCommit=${GIT_COMMIT} \
|
-X ${PACKAGE}.gitCommit=${GIT_COMMIT} \
|
||||||
-X ${PACKAGE}.gitTreeState=${GIT_TREE_STATE}\
|
-X ${PACKAGE}.gitTreeState=${GIT_TREE_STATE}\
|
||||||
-X ${PACKAGE}.kubectlVersion=${KUBECTL_VERSION}\
|
-X ${PACKAGE}.kubectlVersion=${KUBECTL_VERSION}
|
||||||
-X "${PACKAGE}.extraBuildInfo=${EXTRA_BUILD_INFO}"
|
|
||||||
|
|
||||||
ifeq (${STATIC_BUILD}, true)
|
ifeq (${STATIC_BUILD}, true)
|
||||||
override LDFLAGS += -extldflags "-static"
|
override LDFLAGS += -extldflags "-static"
|
||||||
@@ -192,25 +172,29 @@ endif
|
|||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: cli image
|
all: cli image
|
||||||
|
|
||||||
.PHONY: mockgen
|
# We have some legacy requirements for being checked out within $GOPATH.
|
||||||
mockgen:
|
# The ensure-gopath target can be used as dependency to ensure we are running
|
||||||
./hack/generate-mock.sh
|
# within these boundaries.
|
||||||
|
.PHONY: ensure-gopath
|
||||||
|
ensure-gopath:
|
||||||
|
ifneq ("$(PWD)","$(LEGACY_PATH)")
|
||||||
|
@echo "Due to legacy requirements for codegen, repository needs to be checked out within \$$GOPATH"
|
||||||
|
@echo "Location of this repo should be '$(LEGACY_PATH)' but is '$(PWD)'"
|
||||||
|
@exit 1
|
||||||
|
endif
|
||||||
|
|
||||||
.PHONY: gogen
|
.PHONY: gogen
|
||||||
gogen:
|
gogen: ensure-gopath
|
||||||
export GO111MODULE=off
|
export GO111MODULE=off
|
||||||
go generate ./...
|
go generate ./util/argo/...
|
||||||
|
|
||||||
.PHONY: protogen
|
.PHONY: protogen
|
||||||
protogen: mod-vendor-local protogen-fast
|
protogen: ensure-gopath mod-vendor-local
|
||||||
|
|
||||||
.PHONY: protogen-fast
|
|
||||||
protogen-fast:
|
|
||||||
export GO111MODULE=off
|
export GO111MODULE=off
|
||||||
./hack/generate-proto.sh
|
./hack/generate-proto.sh
|
||||||
|
|
||||||
.PHONY: openapigen
|
.PHONY: openapigen
|
||||||
openapigen:
|
openapigen: ensure-gopath
|
||||||
export GO111MODULE=off
|
export GO111MODULE=off
|
||||||
./hack/update-openapi.sh
|
./hack/update-openapi.sh
|
||||||
|
|
||||||
@@ -225,25 +209,19 @@ notification-docs:
|
|||||||
|
|
||||||
|
|
||||||
.PHONY: clientgen
|
.PHONY: clientgen
|
||||||
clientgen:
|
clientgen: ensure-gopath
|
||||||
export GO111MODULE=off
|
export GO111MODULE=off
|
||||||
./hack/update-codegen.sh
|
./hack/update-codegen.sh
|
||||||
|
|
||||||
.PHONY: clidocsgen
|
.PHONY: clidocsgen
|
||||||
clidocsgen:
|
clidocsgen: ensure-gopath
|
||||||
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: ensure-gopath mod-vendor-local notification-docs notification-catalog gogen protogen clientgen openapigen clidocsgen manifests-local
|
||||||
rm -rf vendor/
|
rm -rf vendor/
|
||||||
|
|
||||||
.PHONY: codegen-local-fast
|
|
||||||
codegen-local-fast: mockgen gogen protogen-fast clientgen openapigen clidocsgen manifests-local notification-docs notification-catalog
|
|
||||||
|
|
||||||
.PHONY: codegen
|
.PHONY: codegen
|
||||||
codegen: test-tools-image
|
codegen: test-tools-image
|
||||||
$(call run-in-test-client,make codegen-local)
|
$(call run-in-test-client,make codegen-local)
|
||||||
@@ -254,11 +232,11 @@ 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=0 go build -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
|
||||||
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${GEN_RESOURCES_CLI_NAME} ./hack/gen-resources/cmd
|
CGO_ENABLED=0 go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${GEN_RESOURCES_CLI_NAME} ./hack/gen-resources/cmd
|
||||||
|
|
||||||
.PHONY: release-cli
|
.PHONY: release-cli
|
||||||
release-cli: clean-debug build-ui
|
release-cli: clean-debug build-ui
|
||||||
@@ -273,8 +251,8 @@ release-cli: clean-debug build-ui
|
|||||||
.PHONY: test-tools-image
|
.PHONY: test-tools-image
|
||||||
test-tools-image:
|
test-tools-image:
|
||||||
ifndef SKIP_TEST_TOOLS_IMAGE
|
ifndef SKIP_TEST_TOOLS_IMAGE
|
||||||
$(SUDO) $(DOCKER) build --build-arg UID=$(CONTAINER_UID) -t $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) -f test/container/Dockerfile .
|
$(SUDO) docker build --build-arg UID=$(CONTAINER_UID) -t $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) -f test/container/Dockerfile .
|
||||||
$(SUDO) $(DOCKER) tag $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG)
|
$(SUDO) docker tag $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: manifests-local
|
.PHONY: manifests-local
|
||||||
@@ -288,25 +266,25 @@ manifests: test-tools-image
|
|||||||
# consolidated binary for cli, util, server, repo-server, controller
|
# consolidated binary for cli, util, server, repo-server, controller
|
||||||
.PHONY: argocd-all
|
.PHONY: argocd-all
|
||||||
argocd-all: clean-debug
|
argocd-all: clean-debug
|
||||||
CGO_ENABLED=${CGO_FLAG} GOOS=${GOOS} GOARCH=${GOARCH} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${BIN_NAME} ./cmd
|
CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${BIN_NAME} ./cmd
|
||||||
|
|
||||||
.PHONY: server
|
.PHONY: server
|
||||||
server: clean-debug
|
server: clean-debug
|
||||||
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd
|
CGO_ENABLED=0 go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd
|
||||||
|
|
||||||
.PHONY: repo-server
|
.PHONY: repo-server
|
||||||
repo-server:
|
repo-server:
|
||||||
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-repo-server ./cmd
|
CGO_ENABLED=0 go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-repo-server ./cmd
|
||||||
|
|
||||||
.PHONY: controller
|
.PHONY: controller
|
||||||
controller:
|
controller:
|
||||||
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-application-controller ./cmd
|
CGO_ENABLED=0 go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-application-controller ./cmd
|
||||||
|
|
||||||
.PHONY: build-ui
|
.PHONY: build-ui
|
||||||
build-ui:
|
build-ui:
|
||||||
DOCKER_BUILDKIT=1 $(DOCKER) build -t argocd-ui --platform=$(TARGET_ARCH) --target argocd-ui .
|
DOCKER_BUILDKIT=1 docker build -t argocd-ui --target argocd-ui .
|
||||||
find ./ui/dist -type f -not -name gitkeep -delete
|
find ./ui/dist -type f -not -name gitkeep -delete
|
||||||
$(DOCKER) run -v ${CURRENT_DIR}/ui/dist/app:/tmp/app --rm -t argocd-ui sh -c 'cp -r ./dist/app/* /tmp/app/'
|
docker run -v ${CURRENT_DIR}/ui/dist/app:/tmp/app --rm -t argocd-ui sh -c 'cp -r ./dist/app/* /tmp/app/'
|
||||||
|
|
||||||
.PHONY: image
|
.PHONY: image
|
||||||
ifeq ($(DEV_IMAGE), true)
|
ifeq ($(DEV_IMAGE), true)
|
||||||
@@ -315,29 +293,29 @@ ifeq ($(DEV_IMAGE), true)
|
|||||||
# the dist directory is under .dockerignore.
|
# the dist directory is under .dockerignore.
|
||||||
IMAGE_TAG="dev-$(shell git describe --always --dirty)"
|
IMAGE_TAG="dev-$(shell git describe --always --dirty)"
|
||||||
image: build-ui
|
image: build-ui
|
||||||
DOCKER_BUILDKIT=1 $(DOCKER) build --platform=$(TARGET_ARCH) -t argocd-base --target argocd-base .
|
DOCKER_BUILDKIT=1 docker build --platform=linux/amd64 -t argocd-base --target argocd-base .
|
||||||
CGO_ENABLED=${CGO_FLAG} GOOS=linux GOARCH=amd64 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd ./cmd
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd ./cmd
|
||||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-server
|
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-server
|
||||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-application-controller
|
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-application-controller
|
||||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-repo-server
|
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-repo-server
|
||||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-cmp-server
|
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-cmp-server
|
||||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-dex
|
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-dex
|
||||||
cp Dockerfile.dev dist
|
cp Dockerfile.dev dist
|
||||||
DOCKER_BUILDKIT=1 $(DOCKER) build --platform=$(TARGET_ARCH) -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) -f dist/Dockerfile.dev dist
|
DOCKER_BUILDKIT=1 docker build --platform=linux/amd64 -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) -f dist/Dockerfile.dev dist
|
||||||
else
|
else
|
||||||
image:
|
image:
|
||||||
DOCKER_BUILDKIT=1 $(DOCKER) build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) --platform=$(TARGET_ARCH) .
|
DOCKER_BUILDKIT=1 docker build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) .
|
||||||
endif
|
endif
|
||||||
@if [ "$(DOCKER_PUSH)" = "true" ] ; then $(DOCKER) push $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) ; fi
|
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) ; fi
|
||||||
|
|
||||||
.PHONY: armimage
|
.PHONY: armimage
|
||||||
armimage:
|
armimage:
|
||||||
$(DOCKER) build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG)-arm .
|
docker build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG)-arm .
|
||||||
|
|
||||||
.PHONY: builder-image
|
.PHONY: builder-image
|
||||||
builder-image:
|
builder-image:
|
||||||
$(DOCKER) build -t $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) --target builder .
|
docker build -t $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) --target builder .
|
||||||
@if [ "$(DOCKER_PUSH)" = "true" ] ; then $(DOCKER) push $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) ; fi
|
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) ; fi
|
||||||
|
|
||||||
.PHONY: mod-download
|
.PHONY: mod-download
|
||||||
mod-download: test-tools-image
|
mod-download: test-tools-image
|
||||||
@@ -371,7 +349,7 @@ lint-local:
|
|||||||
golangci-lint --version
|
golangci-lint --version
|
||||||
# NOTE: If you get a "Killed" OOM message, try reducing the value of GOGC
|
# NOTE: If you get a "Killed" OOM message, try reducing the value of GOGC
|
||||||
# See https://github.com/golangci/golangci-lint#memory-usage-of-golangci-lint
|
# See https://github.com/golangci/golangci-lint#memory-usage-of-golangci-lint
|
||||||
GOGC=$(ARGOCD_LINT_GOGC) GOMAXPROCS=2 golangci-lint run --fix --verbose
|
GOGC=$(ARGOCD_LINT_GOGC) GOMAXPROCS=2 golangci-lint run --fix --verbose --timeout 3000s
|
||||||
|
|
||||||
.PHONY: lint-ui
|
.PHONY: lint-ui
|
||||||
lint-ui: test-tools-image
|
lint-ui: test-tools-image
|
||||||
@@ -390,7 +368,7 @@ build: test-tools-image
|
|||||||
# Build all Go code (local version)
|
# Build all Go code (local version)
|
||||||
.PHONY: build-local
|
.PHONY: build-local
|
||||||
build-local:
|
build-local:
|
||||||
GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v `go list ./... | grep -v 'resource_customizations\|test/e2e'`
|
go build -v `go list ./... | grep -v 'resource_customizations\|test/e2e'`
|
||||||
|
|
||||||
# Run all unit tests
|
# Run all unit tests
|
||||||
#
|
#
|
||||||
@@ -405,9 +383,9 @@ test: test-tools-image
|
|||||||
.PHONY: test-local
|
.PHONY: test-local
|
||||||
test-local:
|
test-local:
|
||||||
if test "$(TEST_MODULE)" = ""; then \
|
if test "$(TEST_MODULE)" = ""; then \
|
||||||
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES=`go list ./... | grep -v 'test/e2e'` ./hack/test.sh -args -test.gocoverdir="$(PWD)/test-results"; \
|
./hack/test.sh -coverprofile=coverage.out `go list ./... | grep -v 'test/e2e'`; \
|
||||||
else \
|
else \
|
||||||
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES="$(TEST_MODULE)" ./hack/test.sh -args -test.gocoverdir="$(PWD)/test-results" "$(TEST_MODULE)"; \
|
./hack/test.sh -coverprofile=coverage.out "$(TEST_MODULE)"; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
.PHONY: test-race
|
.PHONY: test-race
|
||||||
@@ -419,9 +397,9 @@ test-race: test-tools-image
|
|||||||
.PHONY: test-race-local
|
.PHONY: test-race-local
|
||||||
test-race-local:
|
test-race-local:
|
||||||
if test "$(TEST_MODULE)" = ""; then \
|
if test "$(TEST_MODULE)" = ""; then \
|
||||||
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES=`go list ./... | grep -v 'test/e2e'` ./hack/test.sh -race -args -test.gocoverdir="$(PWD)/test-results"; \
|
./hack/test.sh -race -coverprofile=coverage.out `go list ./... | grep -v 'test/e2e'`; \
|
||||||
else \
|
else \
|
||||||
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES="$(TEST_MODULE)" ./hack/test.sh -race -args -test.gocoverdir="$(PWD)/test-results"; \
|
./hack/test.sh -race -coverprofile=coverage.out "$(TEST_MODULE)"; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run the E2E test suite. E2E test servers (see start-e2e target) must be
|
# Run the E2E test suite. E2E test servers (see start-e2e target) must be
|
||||||
@@ -435,7 +413,7 @@ test-e2e:
|
|||||||
test-e2e-local: cli-local
|
test-e2e-local: cli-local
|
||||||
# NO_PROXY ensures all tests don't go out through a proxy if one is configured on the test system
|
# NO_PROXY ensures all tests don't go out through a proxy if one is configured on the test system
|
||||||
export GO111MODULE=off
|
export GO111MODULE=off
|
||||||
DIST_DIR=${DIST_DIR} RERUN_FAILS=5 PACKAGES="./test/e2e" ARGOCD_E2E_RECORD=${ARGOCD_E2E_RECORD} ARGOCD_GPG_ENABLED=true NO_PROXY=* ./hack/test.sh -timeout $(ARGOCD_E2E_TEST_TIMEOUT) -v -args -test.gocoverdir="$(PWD)/test-results"
|
ARGOCD_E2E_RECORD=${ARGOCD_E2E_RECORD} ARGOCD_GPG_ENABLED=true NO_PROXY=* ./hack/test.sh -timeout $(ARGOCD_E2E_TEST_TIMEOUT) -v ./test/e2e
|
||||||
|
|
||||||
# Spawns a shell in the test server container for debugging purposes
|
# Spawns a shell in the test server container for debugging purposes
|
||||||
debug-test-server: test-tools-image
|
debug-test-server: test-tools-image
|
||||||
@@ -448,7 +426,7 @@ debug-test-client: test-tools-image
|
|||||||
# Starts e2e server in a container
|
# Starts e2e server in a container
|
||||||
.PHONY: start-e2e
|
.PHONY: start-e2e
|
||||||
start-e2e: test-tools-image
|
start-e2e: test-tools-image
|
||||||
$(DOCKER) version
|
docker version
|
||||||
mkdir -p ${GOCACHE}
|
mkdir -p ${GOCACHE}
|
||||||
$(call run-in-test-server,make ARGOCD_PROCFILE=test/container/Procfile start-e2e-local)
|
$(call run-in-test-server,make ARGOCD_PROCFILE=test/container/Procfile start-e2e-local)
|
||||||
|
|
||||||
@@ -457,7 +435,6 @@ start-e2e: test-tools-image
|
|||||||
start-e2e-local: mod-vendor-local dep-ui-local cli-local
|
start-e2e-local: mod-vendor-local dep-ui-local cli-local
|
||||||
kubectl create ns argocd-e2e || true
|
kubectl create ns argocd-e2e || true
|
||||||
kubectl create ns argocd-e2e-external || true
|
kubectl create ns argocd-e2e-external || true
|
||||||
kubectl create ns argocd-e2e-external-2 || true
|
|
||||||
kubectl config set-context --current --namespace=argocd-e2e
|
kubectl config set-context --current --namespace=argocd-e2e
|
||||||
kustomize build test/manifests/base | kubectl apply -f -
|
kustomize build test/manifests/base | kubectl apply -f -
|
||||||
kubectl apply -f https://raw.githubusercontent.com/open-cluster-management/api/a6845f2ebcb186ec26b832f60c988537a58f3859/cluster/v1alpha1/0000_04_clusters.open-cluster-management.io_placementdecisions.crd.yaml
|
kubectl apply -f https://raw.githubusercontent.com/open-cluster-management/api/a6845f2ebcb186ec26b832f60c988537a58f3859/cluster/v1alpha1/0000_04_clusters.open-cluster-management.io_placementdecisions.crd.yaml
|
||||||
@@ -466,13 +443,6 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
|
|||||||
mkdir -p /tmp/argo-e2e/app/config/gpg/keys && chmod 0700 /tmp/argo-e2e/app/config/gpg/keys
|
mkdir -p /tmp/argo-e2e/app/config/gpg/keys && chmod 0700 /tmp/argo-e2e/app/config/gpg/keys
|
||||||
mkdir -p /tmp/argo-e2e/app/config/gpg/source && chmod 0700 /tmp/argo-e2e/app/config/gpg/source
|
mkdir -p /tmp/argo-e2e/app/config/gpg/source && chmod 0700 /tmp/argo-e2e/app/config/gpg/source
|
||||||
mkdir -p /tmp/argo-e2e/app/config/plugin && chmod 0700 /tmp/argo-e2e/app/config/plugin
|
mkdir -p /tmp/argo-e2e/app/config/plugin && chmod 0700 /tmp/argo-e2e/app/config/plugin
|
||||||
# create folders to hold go coverage results for each component
|
|
||||||
mkdir -p /tmp/coverage/app-controller
|
|
||||||
mkdir -p /tmp/coverage/api-server
|
|
||||||
mkdir -p /tmp/coverage/repo-server
|
|
||||||
mkdir -p /tmp/coverage/applicationset-controller
|
|
||||||
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 \
|
||||||
@@ -485,13 +455,9 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
|
|||||||
ARGOCD_ZJWT_FEATURE_FLAG=always \
|
ARGOCD_ZJWT_FEATURE_FLAG=always \
|
||||||
ARGOCD_IN_CI=$(ARGOCD_IN_CI) \
|
ARGOCD_IN_CI=$(ARGOCD_IN_CI) \
|
||||||
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_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_E2E_TEST=true \
|
ARGOCD_E2E_TEST=true \
|
||||||
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START}
|
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START}
|
||||||
ls -lrt /tmp/coverage
|
|
||||||
|
|
||||||
# Cleans VSCode debug.test files from sub-dirs to prevent them from being included in by golang embed
|
# Cleans VSCode debug.test files from sub-dirs to prevent them from being included in by golang embed
|
||||||
.PHONY: clean-debug
|
.PHONY: clean-debug
|
||||||
@@ -504,7 +470,7 @@ clean: clean-debug
|
|||||||
|
|
||||||
.PHONY: start
|
.PHONY: start
|
||||||
start: test-tools-image
|
start: test-tools-image
|
||||||
$(DOCKER) version
|
docker version
|
||||||
$(call run-in-test-server,make ARGOCD_PROCFILE=test/container/Procfile start-local ARGOCD_START=${ARGOCD_START})
|
$(call run-in-test-server,make ARGOCD_PROCFILE=test/container/Procfile start-local ARGOCD_START=${ARGOCD_START})
|
||||||
|
|
||||||
# Starts a local instance of ArgoCD
|
# Starts a local instance of ArgoCD
|
||||||
@@ -517,11 +483,9 @@ start-local: mod-vendor-local dep-ui-local cli-local
|
|||||||
mkdir -p /tmp/argocd-local
|
mkdir -p /tmp/argocd-local
|
||||||
mkdir -p /tmp/argocd-local/gpg/keys && chmod 0700 /tmp/argocd-local/gpg/keys
|
mkdir -p /tmp/argocd-local/gpg/keys && chmod 0700 /tmp/argocd-local/gpg/keys
|
||||||
mkdir -p /tmp/argocd-local/gpg/source
|
mkdir -p /tmp/argocd-local/gpg/source
|
||||||
REDIS_PASSWORD=$(shell kubectl get secret argocd-redis -o jsonpath='{.data.auth}' | base64 -d) \
|
|
||||||
ARGOCD_ZJWT_FEATURE_FLAG=always \
|
ARGOCD_ZJWT_FEATURE_FLAG=always \
|
||||||
ARGOCD_IN_CI=false \
|
ARGOCD_IN_CI=false \
|
||||||
ARGOCD_GPG_ENABLED=$(ARGOCD_GPG_ENABLED) \
|
ARGOCD_GPG_ENABLED=$(ARGOCD_GPG_ENABLED) \
|
||||||
BIN_MODE=$(ARGOCD_BIN_MODE) \
|
|
||||||
ARGOCD_E2E_TEST=false \
|
ARGOCD_E2E_TEST=false \
|
||||||
ARGOCD_APPLICATION_NAMESPACES=$(ARGOCD_APPLICATION_NAMESPACES) \
|
ARGOCD_APPLICATION_NAMESPACES=$(ARGOCD_APPLICATION_NAMESPACES) \
|
||||||
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START}
|
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START}
|
||||||
@@ -555,7 +519,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 --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 +527,8 @@ 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}/site:/site -w /site --entrypoint "" ${MKDOCS_DOCKER_IMAGE} python3 -m http.server --bind 0.0.0.0 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
|
||||||
@@ -586,8 +551,7 @@ install-tools-local: install-test-tools-local install-codegen-tools-local instal
|
|||||||
.PHONY: install-test-tools-local
|
.PHONY: install-test-tools-local
|
||||||
install-test-tools-local:
|
install-test-tools-local:
|
||||||
./hack/install.sh kustomize
|
./hack/install.sh kustomize
|
||||||
./hack/install.sh helm
|
./hack/install.sh helm-linux
|
||||||
./hack/install.sh gotestsum
|
|
||||||
|
|
||||||
# Installs all tools required for running codegen (Linux packages)
|
# Installs all tools required for running codegen (Linux packages)
|
||||||
.PHONY: install-codegen-tools-local
|
.PHONY: install-codegen-tools-local
|
||||||
@@ -615,7 +579,7 @@ list:
|
|||||||
|
|
||||||
.PHONY: applicationset-controller
|
.PHONY: applicationset-controller
|
||||||
applicationset-controller:
|
applicationset-controller:
|
||||||
GODEBUG="tarinsecurepath=0,zipinsecurepath=0" CGO_ENABLED=${CGO_FLAG} go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-applicationset-controller ./cmd
|
CGO_ENABLED=0 go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-applicationset-controller ./cmd
|
||||||
|
|
||||||
.PHONY: checksums
|
.PHONY: checksums
|
||||||
checksums:
|
checksums:
|
||||||
@@ -632,55 +596,3 @@ snyk-non-container-tests:
|
|||||||
.PHONY: snyk-report
|
.PHONY: snyk-report
|
||||||
snyk-report:
|
snyk-report:
|
||||||
./hack/snyk-report.sh $(target_branch)
|
./hack/snyk-report.sh $(target_branch)
|
||||||
|
|
||||||
.PHONY: help
|
|
||||||
help:
|
|
||||||
@echo 'Note: Generally an item w/ (-local) will run inside docker unless you use the -local variant'
|
|
||||||
@echo
|
|
||||||
@echo 'Common targets'
|
|
||||||
@echo
|
|
||||||
@echo 'all -- make cli and image'
|
|
||||||
@echo
|
|
||||||
@echo 'components:'
|
|
||||||
@echo ' applicationset-controller -- applicationset controller'
|
|
||||||
@echo ' cli(-local) -- argocd cli program'
|
|
||||||
@echo ' controller -- controller (orchestrator)'
|
|
||||||
@echo ' repo-server -- repo server (manage repository instances)'
|
|
||||||
@echo ' server -- argocd web application'
|
|
||||||
@echo
|
|
||||||
@echo 'build:'
|
|
||||||
@echo ' image -- make image of the following items'
|
|
||||||
@echo ' build(-local) -- compile go'
|
|
||||||
@echo ' build-docs(-local) -- build docs'
|
|
||||||
@echo ' build-ui -- compile typescript'
|
|
||||||
@echo
|
|
||||||
@echo 'run:'
|
|
||||||
@echo ' run -- run the components locally'
|
|
||||||
@echo ' serve-docs(-local) -- expose the documents for viewing in a browser'
|
|
||||||
@echo
|
|
||||||
@echo 'release:'
|
|
||||||
@echo ' release-cli'
|
|
||||||
@echo ' release-precheck'
|
|
||||||
@echo ' checksums'
|
|
||||||
@echo
|
|
||||||
@echo 'docs:'
|
|
||||||
@echo ' build-docs(-local)'
|
|
||||||
@echo ' serve-docs(-local)'
|
|
||||||
@echo ' notification-docs'
|
|
||||||
@echo ' clidocsgen'
|
|
||||||
@echo
|
|
||||||
@echo 'testing:'
|
|
||||||
@echo ' test(-local)'
|
|
||||||
@echo ' start-e2e(-local)'
|
|
||||||
@echo ' test-e2e(-local)'
|
|
||||||
@echo ' test-race(-local)'
|
|
||||||
@echo
|
|
||||||
@echo 'debug:'
|
|
||||||
@echo ' list -- list all make targets'
|
|
||||||
@echo ' install-tools-local -- install all the tools below'
|
|
||||||
@echo ' install-lint-tools(-local)'
|
|
||||||
@echo
|
|
||||||
@echo 'codegen:'
|
|
||||||
@echo ' codegen(-local) -- if using -local, run the following targets first'
|
|
||||||
@echo ' install-codegen-tools-local -- run this to install the codegen tools'
|
|
||||||
@echo ' install-go-tools-local -- run this to install go libraries for codegen'
|
|
||||||
|
|||||||
3
OWNERS
3
OWNERS
@@ -1,12 +1,10 @@
|
|||||||
owners:
|
owners:
|
||||||
- alexmt
|
- alexmt
|
||||||
- crenshaw-dev
|
|
||||||
- jessesuen
|
- jessesuen
|
||||||
|
|
||||||
approvers:
|
approvers:
|
||||||
- alexec
|
- alexec
|
||||||
- alexmt
|
- alexmt
|
||||||
- gdsoumya
|
|
||||||
- jannfis
|
- jannfis
|
||||||
- jessesuen
|
- jessesuen
|
||||||
- jgwest
|
- jgwest
|
||||||
@@ -31,4 +29,3 @@ reviewers:
|
|||||||
- saumeya
|
- saumeya
|
||||||
- zachaller
|
- zachaller
|
||||||
- 34fathombelow
|
- 34fathombelow
|
||||||
- alexef
|
|
||||||
|
|||||||
17
Procfile
17
Procfile
@@ -1,13 +1,12 @@
|
|||||||
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 "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''}"
|
||||||
api-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "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 "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''}"
|
||||||
dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v2/cmd gendexcfg -o `pwd`/dist/dex.yaml && (test -f dist/dex.yaml || { echo 'Failed to generate dex configuration'; exit 1; }) && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:$(grep "image: ghcr.io/dexidp/dex" manifests/base/dex/argocd-dex-server-deployment.yaml | cut -d':' -f3) dex serve /dex.yaml"
|
dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v2/cmd gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:$(grep "image: ghcr.io/dexidp/dex" manifests/base/dex/argocd-dex-server-deployment.yaml | cut -d':' -f3) dex serve /dex.yaml"
|
||||||
redis: 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} 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 "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
|
||||||
dev-mounter: [[ "$ARGOCD_E2E_TEST" != "true" ]] && go run hack/dev-mounter/main.go --configmap argocd-ssh-known-hosts-cm=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} --configmap argocd-tls-certs-cm=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} --configmap argocd-gpg-keys-cm=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source}
|
dev-mounter: [[ "$ARGOCD_E2E_TEST" != "true" ]] && go run hack/dev-mounter/main.go --configmap argocd-ssh-known-hosts-cm=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} --configmap argocd-tls-certs-cm=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} --configmap argocd-gpg-keys-cm=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source}
|
||||||
applicationset-controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/applicationset-controller} FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-applicationset-controller $COMMAND --loglevel debug --metrics-addr localhost:12345 --probe-addr localhost:12346 --argocd-repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
|
applicationset-controller: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_ASK_PASS_SOCK=/tmp/applicationset-ask-pass.sock ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-applicationset-controller $COMMAND --loglevel debug --metrics-addr localhost:12345 --probe-addr localhost:12346 --argocd-repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
|
||||||
notification: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/notification} FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_BINARY_NAME=argocd-notifications $COMMAND --loglevel debug --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} --self-service-notification-enabled=${ARGOCD_NOTIFICATION_CONTROLLER_SELF_SERVICE_NOTIFICATION_ENABLED:-'false'}"
|
notification: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_BINARY_NAME=argocd-notifications $COMMAND --loglevel debug"
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
**Releases:**
|
**Releases:**
|
||||||
[](https://github.com/argoproj/argo-cd/releases/latest)
|
[](https://github.com/argoproj/argo-cd/releases/latest)
|
||||||
[](https://artifacthub.io/packages/helm/argo/argo-cd)
|
[](https://artifacthub.io/packages/helm/argo/argo-cd)
|
||||||
[](https://slsa.dev)
|
|
||||||
|
|
||||||
**Code:**
|
**Code:**
|
||||||
[](https://github.com/argoproj/argo-cd/actions?query=workflow%3A%22Integration+tests%22)
|
[](https://github.com/argoproj/argo-cd/actions?query=workflow%3A%22Integration+tests%22)
|
||||||
[](https://codecov.io/gh/argoproj/argo-cd)
|
[](https://codecov.io/gh/argoproj/argo-cd)
|
||||||
[](https://bestpractices.coreinfrastructure.org/projects/4486)
|
[](https://bestpractices.coreinfrastructure.org/projects/4486)
|
||||||
[](https://scorecard.dev/viewer/?uri=github.com/argoproj/argo-cd)
|
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fargoproj%2Fargo-cd?ref=badge_shield)
|
||||||
|
|
||||||
**Social:**
|
**Social:**
|
||||||
[](https://twitter.com/argoproj)
|
[](https://twitter.com/argoproj)
|
||||||
[](https://argoproj.github.io/community/join-slack)
|
[](https://argoproj.github.io/community/join-slack)
|
||||||
[](https://www.linkedin.com/company/argoproj/)
|
|
||||||
|
|
||||||
# Argo CD - Declarative Continuous Delivery for Kubernetes
|
# Argo CD - Declarative Continuous Delivery for Kubernetes
|
||||||
|
|
||||||
@@ -56,7 +54,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://blog.akuity.io/unveil-the-secret-ingredients-of-continuous-delivery-at-enterprise-scale-with-argo-cd-7c5b4057ee49)
|
||||||
1. [GitOps Without Pipelines With ArgoCD Image Updater](https://youtu.be/avPUQin9kzU)
|
1. [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)
|
||||||
@@ -82,8 +80,7 @@ Participation in the Argo CD project is governed by the [CNCF Code of Conduct](h
|
|||||||
1. [Applied GitOps with Argo CD](https://thenewstack.io/applied-gitops-with-argocd/)
|
1. [Applied GitOps with Argo CD](https://thenewstack.io/applied-gitops-with-argocd/)
|
||||||
1. [Solving configuration drift using GitOps with Argo CD](https://www.cncf.io/blog/2020/12/17/solving-configuration-drift-using-gitops-with-argo-cd/)
|
1. [Solving configuration drift using GitOps with Argo CD](https://www.cncf.io/blog/2020/12/17/solving-configuration-drift-using-gitops-with-argo-cd/)
|
||||||
1. [Decentralized GitOps over environments](https://blogs.sap.com/2021/05/06/decentralized-gitops-over-environments/)
|
1. [Decentralized GitOps over environments](https://blogs.sap.com/2021/05/06/decentralized-gitops-over-environments/)
|
||||||
|
1. [How GitOps and Operators mark the rise of Infrastructure-As-Software](https://paytmlabs.com/blog/2021/10/how-to-improve-operational-work-with-operators-and-gitops/)
|
||||||
1. [Getting Started with ArgoCD for GitOps Deployments](https://youtu.be/AvLuplh1skA)
|
1. [Getting Started with ArgoCD for GitOps Deployments](https://youtu.be/AvLuplh1skA)
|
||||||
1. [Using Argo CD & Datree for Stable Kubernetes CI/CD Deployments](https://youtu.be/17894DTru2Y)
|
1. [Using Argo CD & Datree for Stable Kubernetes CI/CD Deployments](https://youtu.be/17894DTru2Y)
|
||||||
1. [How to create Argo CD Applications Automatically using ApplicationSet? "Automation of GitOps"](https://amralaayassen.medium.com/how-to-create-argocd-applications-automatically-using-applicationset-automation-of-the-gitops-59455eaf4f72)
|
|
||||||
1. [Progressive Delivery with Service Mesh – Argo Rollouts with Istio](https://www.cncf.io/blog/2022/12/16/progressive-delivery-with-service-mesh-argo-rollouts-with-istio/)
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,128 +0,0 @@
|
|||||||
header:
|
|
||||||
schema-version: 1.0.0
|
|
||||||
expiration-date: '2024-10-31T00:00:00.000Z' # One year from initial release.
|
|
||||||
last-updated: '2023-10-27'
|
|
||||||
last-reviewed: '2023-10-27'
|
|
||||||
commit-hash: 74a367d10e7110209610ba3ec225539ebe5f7522
|
|
||||||
project-url: https://github.com/argoproj/argo-cd
|
|
||||||
project-release: v2.14.0
|
|
||||||
changelog: https://github.com/argoproj/argo-cd/releases
|
|
||||||
license: https://github.com/argoproj/argo-cd/blob/master/LICENSE
|
|
||||||
project-lifecycle:
|
|
||||||
status: active
|
|
||||||
roadmap: https://github.com/orgs/argoproj/projects/25
|
|
||||||
bug-fixes-only: false
|
|
||||||
core-maintainers:
|
|
||||||
- https://github.com/argoproj/argoproj/blob/master/MAINTAINERS.md
|
|
||||||
release-cycle: https://argo-cd.readthedocs.io/en/stable/developer-guide/release-process-and-cadence/
|
|
||||||
release-process: https://argo-cd.readthedocs.io/en/stable/developer-guide/release-process-and-cadence/#release-process
|
|
||||||
contribution-policy:
|
|
||||||
accepts-pull-requests: true
|
|
||||||
accepts-automated-pull-requests: true
|
|
||||||
automated-tools-list:
|
|
||||||
- automated-tool: dependabot
|
|
||||||
action: allowed
|
|
||||||
path:
|
|
||||||
- /
|
|
||||||
- automated-tool: snyk-report
|
|
||||||
action: allowed
|
|
||||||
path:
|
|
||||||
- docs/snyk
|
|
||||||
comment: |
|
|
||||||
This tool runs Snyk and generates a report of vulnerabilities in the project's dependencies. The report is
|
|
||||||
placed in the project's documentation. The workflow is defined here:
|
|
||||||
https://github.com/argoproj/argo-cd/blob/master/.github/workflows/update-snyk.yaml
|
|
||||||
contributing-policy: https://argo-cd.readthedocs.io/en/stable/developer-guide/code-contributions/
|
|
||||||
code-of-conduct: https://github.com/cncf/foundation/blob/master/code-of-conduct.md
|
|
||||||
documentation:
|
|
||||||
- https://argo-cd.readthedocs.io/
|
|
||||||
distribution-points:
|
|
||||||
- https://github.com/argoproj/argo-cd/releases
|
|
||||||
- https://quay.io/repository/argoproj/argocd
|
|
||||||
security-artifacts:
|
|
||||||
threat-model:
|
|
||||||
threat-model-created: true
|
|
||||||
evidence-url:
|
|
||||||
- https://github.com/argoproj/argoproj/blob/master/docs/argo_threat_model.pdf
|
|
||||||
- https://github.com/argoproj/argoproj/blob/master/docs/end_user_threat_model.pdf
|
|
||||||
self-assessment:
|
|
||||||
self-assessment-created: false
|
|
||||||
comment: |
|
|
||||||
An extensive self-assessment was performed for CNCF graduation. Because the self-assessment process was evolving
|
|
||||||
at the time, no standardized document has been published.
|
|
||||||
security-testing:
|
|
||||||
- tool-type: sca
|
|
||||||
tool-name: Dependabot
|
|
||||||
tool-version: "2"
|
|
||||||
tool-url: https://github.com/dependabot
|
|
||||||
integration:
|
|
||||||
ad-hoc: false
|
|
||||||
ci: false
|
|
||||||
before-release: false
|
|
||||||
tool-rulesets:
|
|
||||||
- https://github.com/argoproj/argo-cd/blob/master/.github/dependabot.yml
|
|
||||||
- tool-type: sca
|
|
||||||
tool-name: Snyk
|
|
||||||
tool-version: latest
|
|
||||||
tool-url: https://snyk.io/
|
|
||||||
integration:
|
|
||||||
ad-hoc: true
|
|
||||||
ci: true
|
|
||||||
before-release: false
|
|
||||||
- tool-type: sast
|
|
||||||
tool-name: CodeQL
|
|
||||||
tool-version: latest
|
|
||||||
tool-url: https://codeql.github.com/
|
|
||||||
integration:
|
|
||||||
ad-hoc: false
|
|
||||||
ci: true
|
|
||||||
before-release: false
|
|
||||||
comment: |
|
|
||||||
We use the default configuration with the latest version.
|
|
||||||
security-assessments:
|
|
||||||
- auditor-name: Trail of Bits
|
|
||||||
auditor-url: https://trailofbits.com
|
|
||||||
auditor-report: https://github.com/argoproj/argoproj/blob/master/docs/argo_security_final_report.pdf
|
|
||||||
report-year: 2021
|
|
||||||
- auditor-name: Ada Logics
|
|
||||||
auditor-url: https://adalogics.com
|
|
||||||
auditor-report: https://github.com/argoproj/argoproj/blob/master/docs/argo_security_audit_2022.pdf
|
|
||||||
report-year: 2022
|
|
||||||
- auditor-name: Ada Logics
|
|
||||||
auditor-url: https://adalogics.com
|
|
||||||
auditor-report: https://github.com/argoproj/argoproj/blob/master/docs/audit_fuzzer_adalogics_2022.pdf
|
|
||||||
report-year: 2022
|
|
||||||
comment: |
|
|
||||||
Part of the audit was performed by Ada Logics, focussed on fuzzing.
|
|
||||||
- auditor-name: Chainguard
|
|
||||||
auditor-url: https://chainguard.dev
|
|
||||||
auditor-report: https://github.com/argoproj/argoproj/blob/master/docs/software_supply_chain_slsa_assessment_chainguard_2023.pdf
|
|
||||||
report-year: 2023
|
|
||||||
comment: |
|
|
||||||
Confirmed the project's release process as achieving SLSA (v0.1) level 3.
|
|
||||||
security-contacts:
|
|
||||||
- type: email
|
|
||||||
value: cncf-argo-security@lists.cncf.io
|
|
||||||
primary: true
|
|
||||||
vulnerability-reporting:
|
|
||||||
accepts-vulnerability-reports: true
|
|
||||||
email-contact: cncf-argo-security@lists.cncf.io
|
|
||||||
security-policy: https://github.com/argoproj/argo-cd/security/policy
|
|
||||||
bug-bounty-available: true
|
|
||||||
bug-bounty-url: https://hackerone.com/ibb/policy_scopes
|
|
||||||
out-scope:
|
|
||||||
- vulnerable and outdated components # See https://github.com/argoproj/argo-cd/blob/master/SECURITY.md#a-word-about-security-scanners
|
|
||||||
- security logging and monitoring failures
|
|
||||||
dependencies:
|
|
||||||
third-party-packages: true
|
|
||||||
dependencies-lists:
|
|
||||||
- https://github.com/argoproj/argo-cd/blob/master/go.mod
|
|
||||||
- https://github.com/argoproj/argo-cd/blob/master/Dockerfile
|
|
||||||
- https://github.com/argoproj/argo-cd/blob/master/ui/package.json
|
|
||||||
sbom:
|
|
||||||
- sbom-file: https://github.com/argoproj/argo-cd/releases # Every release's assets include SBOMs.
|
|
||||||
sbom-format: SPDX
|
|
||||||
dependencies-lifecycle:
|
|
||||||
policy-url: https://argo-cd.readthedocs.io/en/stable/developer-guide/release-process-and-cadence/#dependencies-lifecycle-policy
|
|
||||||
env-dependencies-policy:
|
|
||||||
policy-url: https://argo-cd.readthedocs.io/en/stable/developer-guide/release-process-and-cadence/#dependencies-lifecycle-policy
|
|
||||||
33
SECURITY.md
33
SECURITY.md
@@ -1,6 +1,6 @@
|
|||||||
# Security Policy for Argo CD
|
# Security Policy for Argo CD
|
||||||
|
|
||||||
Version: **v1.5 (2023-03-06)**
|
Version: **v1.4 (2022-01-23)**
|
||||||
|
|
||||||
## Preface
|
## Preface
|
||||||
|
|
||||||
@@ -35,11 +35,13 @@ impact on Argo CD before opening an issue at least roughly.
|
|||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
We currently support the last 3 minor versions of Argo CD with security and bug fixes.
|
We currently support the most recent release (`N`, e.g. `1.8`) and the release
|
||||||
|
previous to the most recent one (`N-1`, e.g. `1.7`). With the release of
|
||||||
|
`N+1`, `N-1` drops out of support and `N` becomes `N-1`.
|
||||||
|
|
||||||
We regularly perform patch releases (e.g. `1.8.5` and `1.7.12`) for the
|
We regularly perform patch releases (e.g. `1.8.5` and `1.7.12`) for the
|
||||||
supported versions, which will contain fixes for security vulnerabilities and
|
supported versions, which will contain fixes for security vulnerabilities and
|
||||||
important bugs. Prior releases might receive critical security fixes on best
|
important bugs. Prior releases might receive critical security fixes on a best
|
||||||
effort basis, however, it cannot be guaranteed that security fixes get
|
effort basis, however, it cannot be guaranteed that security fixes get
|
||||||
back-ported to these unsupported versions.
|
back-ported to these unsupported versions.
|
||||||
|
|
||||||
@@ -50,7 +52,7 @@ of releasing it within a patch branch for the currently supported releases.
|
|||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
If you find a security related bug in Argo CD, we kindly ask you for responsible
|
If you find a security related bug in ArgoCD, we kindly ask you for responsible
|
||||||
disclosure and for giving us appropriate time to react, analyze and develop a
|
disclosure and for giving us appropriate time to react, analyze and develop a
|
||||||
fix to mitigate the found security vulnerability.
|
fix to mitigate the found security vulnerability.
|
||||||
|
|
||||||
@@ -59,28 +61,13 @@ and disclosure with you. Sometimes, it might take a little longer for us to
|
|||||||
react (e.g. out of office conditions), so please bear with us in these cases.
|
react (e.g. out of office conditions), so please bear with us in these cases.
|
||||||
|
|
||||||
We will publish security advisories using the
|
We will publish security advisories using the
|
||||||
[GitHub Security Advisories](https://github.com/argoproj/argo-cd/security/advisories)
|
[Git Hub Security Advisories](https://github.com/argoproj/argo-cd/security/advisories)
|
||||||
feature to keep our community well-informed, and will credit you for your
|
feature to keep our community well informed, and will credit you for your
|
||||||
findings (unless you prefer to stay anonymous, of course).
|
findings (unless you prefer to stay anonymous, of course).
|
||||||
|
|
||||||
There are two ways to report a vulnerability to the Argo CD team:
|
Please report vulnerabilities by e-mail to the following address:
|
||||||
|
|
||||||
* By opening a draft GitHub security advisory: https://github.com/argoproj/argo-cd/security/advisories/new
|
* cncf-argo-security@lists.cncf.io
|
||||||
* By e-mail to the following address: cncf-argo-security@lists.cncf.io
|
|
||||||
|
|
||||||
## Internet Bug Bounty collaboration
|
|
||||||
|
|
||||||
We're happy to announce that the Argo project is collaborating with the great
|
|
||||||
folks over at
|
|
||||||
[Hacker One](https://hackerone.com/) and their
|
|
||||||
[Internet Bug Bounty program](https://hackerone.com/ibb)
|
|
||||||
to reward the awesome people who find security vulnerabilities in the four
|
|
||||||
main Argo projects (CD, Events, Rollouts and Workflows) and then work with
|
|
||||||
us to fix and disclose them in a responsible manner.
|
|
||||||
|
|
||||||
If you report a vulnerability to us as outlined in this security policy, we
|
|
||||||
will work together with you to find out whether your finding is eligible for
|
|
||||||
claiming a bounty, and also on how to claim it.
|
|
||||||
|
|
||||||
## Securing your Argo CD Instance
|
## Securing your Argo CD Instance
|
||||||
|
|
||||||
|
|||||||
140
USERS.md
140
USERS.md
@@ -7,96 +7,61 @@ Currently, the following organizations are **officially** using Argo CD:
|
|||||||
|
|
||||||
1. [127Labs](https://127labs.com/)
|
1. [127Labs](https://127labs.com/)
|
||||||
1. [3Rein](https://www.3rein.com/)
|
1. [3Rein](https://www.3rein.com/)
|
||||||
1. [4data](https://4data.ch/)
|
|
||||||
1. [7shifts](https://www.7shifts.com/)
|
1. [7shifts](https://www.7shifts.com/)
|
||||||
1. [Adevinta](https://www.adevinta.com/)
|
1. [Adevinta](https://www.adevinta.com/)
|
||||||
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. [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. [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/)
|
||||||
1. [AlphaSense](https://www.alpha-sense.com/)
|
|
||||||
1. [Amadeus IT Group](https://amadeus.com/)
|
1. [Amadeus IT Group](https://amadeus.com/)
|
||||||
1. [Ambassador Labs](https://www.getambassador.io/)
|
1. [Ambassador Labs](https://www.getambassador.io/)
|
||||||
1. [Ancestry](https://www.ancestry.com/)
|
|
||||||
1. [Andgo Systems](https://www.andgosystems.com/)
|
|
||||||
1. [ANSTO - Australian Synchrotron](https://www.synchrotron.org.au/)
|
1. [ANSTO - Australian Synchrotron](https://www.synchrotron.org.au/)
|
||||||
1. [Ant Group](https://www.antgroup.com/)
|
1. [Ant Group](https://www.antgroup.com/)
|
||||||
1. [AppDirect](https://www.appdirect.com)
|
1. [AppDirect](https://www.appdirect.com)
|
||||||
1. [Arctiq Inc.](https://www.arctiq.ca)
|
1. [Arctiq Inc.](https://www.arctiq.ca)
|
||||||
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. [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. [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. [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. [PT Boer Technology (Btech)](https://btech.id/)
|
||||||
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. [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. [Camptocamp](https://camptocamp.com)
|
1. [Camptocamp](https://camptocamp.com)
|
||||||
1. [Candis](https://www.candis.io)
|
|
||||||
1. [Capital One](https://www.capitalone.com)
|
1. [Capital One](https://www.capitalone.com)
|
||||||
1. [CARFAX Europe](https://www.carfax.eu)
|
|
||||||
1. [CARFAX](https://www.carfax.com)
|
1. [CARFAX](https://www.carfax.com)
|
||||||
1. [Carrefour Group](https://www.carrefour.com)
|
1. [CARFAX Europe](https://www.carfax.eu)
|
||||||
1. [Casavo](https://casavo.com)
|
1. [Casavo](https://casavo.com)
|
||||||
1. [Celonis](https://www.celonis.com/)
|
1. [Celonis](https://www.celonis.com/)
|
||||||
1. [CERN](https://home.cern/)
|
1. [CERN](https://home.cern/)
|
||||||
1. [Chainnodes](https://chainnodes.org)
|
|
||||||
1. [Chargetrip](https://chargetrip.com)
|
1. [Chargetrip](https://chargetrip.com)
|
||||||
1. [Chime](https://www.chime.com)
|
1. [Chime](https://www.chime.com)
|
||||||
1. [Cisco ET&I](https://eti.cisco.com/)
|
1. [Cisco ET&I](https://eti.cisco.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. [Cloudmate](https://cloudmt.co.kr/)
|
|
||||||
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. [Coralogix](https://coralogix.com/)
|
1. [Coralogix](https://coralogix.com/)
|
||||||
1. [Crédit Agricole CIB](https://www.ca-cib.com)
|
|
||||||
1. [CROZ d.o.o.](https://croz.net/)
|
1. [CROZ d.o.o.](https://croz.net/)
|
||||||
|
1. [Crédit Agricole CIB](https://www.ca-cib.com)
|
||||||
1. [CyberAgent](https://www.cyberagent.co.jp/en/)
|
1. [CyberAgent](https://www.cyberagent.co.jp/en/)
|
||||||
1. [Cybozu](https://cybozu-global.com)
|
1. [Cybozu](https://cybozu-global.com)
|
||||||
1. [D2iQ](https://www.d2iq.com)
|
1. [D2iQ](https://www.d2iq.com)
|
||||||
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/)
|
||||||
1. [Devtron Labs](https://github.com/devtron-labs/devtron)
|
1. [Devtron Labs](https://github.com/devtron-labs/devtron)
|
||||||
1. [DigitalOcean](https://www.digitalocean.com)
|
|
||||||
1. [Divistant](https://divistant.com)
|
1. [Divistant](https://divistant.com)
|
||||||
1. [Dott](https://ridedott.com)
|
|
||||||
1. [Doximity](https://www.doximity.com/)
|
1. [Doximity](https://www.doximity.com/)
|
||||||
1. [EDF Renewables](https://www.edf-re.com/)
|
1. [EDF Renewables](https://www.edf-re.com/)
|
||||||
1. [edX](https://edx.org)
|
1. [edX](https://edx.org)
|
||||||
@@ -108,92 +73,62 @@ Currently, the following organizations are **officially** using Argo CD:
|
|||||||
1. [Energisme](https://energisme.com/)
|
1. [Energisme](https://energisme.com/)
|
||||||
1. [enigmo](https://enigmo.co.jp/)
|
1. [enigmo](https://enigmo.co.jp/)
|
||||||
1. [Envoy](https://envoy.com/)
|
1. [Envoy](https://envoy.com/)
|
||||||
1. [Factorial](https://factorialhr.com/)
|
|
||||||
1. [Farfetch](https://www.farfetch.com)
|
|
||||||
1. [Faro](https://www.faro.com/)
|
1. [Faro](https://www.faro.com/)
|
||||||
1. [Fave](https://myfave.com)
|
1. [Fave](https://myfave.com)
|
||||||
1. [Flexport](https://www.flexport.com/)
|
|
||||||
1. [Flip](https://flip.id)
|
1. [Flip](https://flip.id)
|
||||||
1. [Fly Security](https://www.flysecurity.com.br/)
|
|
||||||
1. [Fonoa](https://www.fonoa.com/)
|
1. [Fonoa](https://www.fonoa.com/)
|
||||||
1. [Fortra](https://www.fortra.com)
|
|
||||||
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)
|
||||||
1. [gloat](https://gloat.com/)
|
1. [gloat](https://gloat.com/)
|
||||||
1. [GLOBIS](https://globis.com)
|
1. [GLOBIS](https://globis.com)
|
||||||
1. [Glovo](https://www.glovoapp.com)
|
1. [Glovo](https://www.glovoapp.com)
|
||||||
1. [GlueOps](https://glueops.dev)
|
|
||||||
1. [GMETRI](https://gmetri.com/)
|
1. [GMETRI](https://gmetri.com/)
|
||||||
1. [Gojek](https://www.gojek.io/)
|
1. [Gojek](https://www.gojek.io/)
|
||||||
1. [GoTo Financial](https://gotofinancial.com/)
|
|
||||||
1. [GoTo](https://www.goto.com/)
|
|
||||||
1. [Greenpass](https://www.greenpass.com.br/)
|
1. [Greenpass](https://www.greenpass.com.br/)
|
||||||
1. [Gridfuse](https://gridfuse.com/)
|
1. [Gridfuse](https://gridfuse.com/)
|
||||||
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)
|
||||||
1. [hipages](https://hipages.com.au/)
|
1. [hipages](https://hipages.com.au/)
|
||||||
1. [Hiya](https://hiya.com)
|
1. [Hiya](https://hiya.com)
|
||||||
1. [Honestbank](https://honestbank.com)
|
1. [Honestbank](https://honestbank.com)
|
||||||
1. [Hostinger](https://www.hostinger.com)
|
|
||||||
1. [IABAI](https://www.iab.ai)
|
|
||||||
1. [IBM](https://www.ibm.com/)
|
1. [IBM](https://www.ibm.com/)
|
||||||
1. [Ibotta](https://home.ibotta.com)
|
1. [Ibotta](https://home.ibotta.com)
|
||||||
1. [IFS](https://www.ifs.com)
|
|
||||||
1. [IITS-Consulting](https://iits-consulting.de)
|
1. [IITS-Consulting](https://iits-consulting.de)
|
||||||
1. [IllumiDesk](https://www.illumidesk.com)
|
|
||||||
1. [imaware](https://imaware.health)
|
1. [imaware](https://imaware.health)
|
||||||
1. [Indeed](https://indeed.com)
|
1. [Indeed](https://indeed.com)
|
||||||
1. [Index Exchange](https://www.indexexchange.com/)
|
1. [Index Exchange](https://www.indexexchange.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. [Joblift](https://joblift.com/)
|
1. [Joblift](https://joblift.com/)
|
||||||
1. [JovianX](https://www.jovianx.com/)
|
1. [JovianX](https://www.jovianx.com/)
|
||||||
1. [Kaltura](https://corp.kaltura.com/)
|
1. [Kaltura](https://corp.kaltura.com/)
|
||||||
1. [Kandji](https://www.kandji.io/)
|
1. [Kandji](https://www.kandji.io/)
|
||||||
1. [Karrot](https://www.daangn.com/)
|
|
||||||
1. [KarrotPay](https://www.daangnpay.com/)
|
1. [KarrotPay](https://www.daangnpay.com/)
|
||||||
|
1. [Karrot](https://www.daangn.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. [Keptn](https://keptn.sh)
|
1. [Keptn](https://keptn.sh)
|
||||||
1. [Kinguin](https://www.kinguin.net/)
|
1. [Kinguin](https://www.kinguin.net/)
|
||||||
1. [KintoHub](https://www.kintohub.com/)
|
1. [KintoHub](https://www.kintohub.com/)
|
||||||
1. [KompiTech GmbH](https://www.kompitech.com/)
|
1. [KompiTech GmbH](https://www.kompitech.com/)
|
||||||
1. [Kong Inc.](https://konghq.com/)
|
|
||||||
1. [KPMG](https://kpmg.com/uk)
|
|
||||||
1. [KubeSphere](https://github.com/kubesphere)
|
1. [KubeSphere](https://github.com/kubesphere)
|
||||||
1. [Kurly](https://www.kurly.com/)
|
1. [Kurly](https://www.kurly.com/)
|
||||||
1. [Kvist](https://kvistsolutions.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)
|
||||||
1. [Lightricks](https://www.lightricks.com/)
|
1. [Lightricks](https://www.lightricks.com/)
|
||||||
1. [LINE](https://linecorp.com/en/)
|
1. [LINE](https://linecorp.com/en/)
|
||||||
1. [Loom](https://www.loom.com/)
|
|
||||||
1. [Lucid Motors](https://www.lucidmotors.com/)
|
|
||||||
1. [Lytt](https://www.lytt.co/)
|
1. [Lytt](https://www.lytt.co/)
|
||||||
1. [Magic Leap](https://www.magicleap.com/)
|
1. [Magic Leap](https://www.magicleap.com/)
|
||||||
1. [Majid Al Futtaim](https://www.majidalfuttaim.com/)
|
1. [Majid Al Futtaim](https://www.majidalfuttaim.com/)
|
||||||
@@ -204,43 +139,28 @@ Currently, the following organizations are **officially** using Argo CD:
|
|||||||
1. [Max Kelsen](https://www.maxkelsen.com/)
|
1. [Max Kelsen](https://www.maxkelsen.com/)
|
||||||
1. [MeDirect](https://medirect.com.mt/)
|
1. [MeDirect](https://medirect.com.mt/)
|
||||||
1. [Meican](https://meican.com/)
|
1. [Meican](https://meican.com/)
|
||||||
1. [Meilleurs Agents](https://www.meilleursagents.com/)
|
|
||||||
1. [Mercedes-Benz Tech Innovation](https://www.mercedes-benz-techinnovation.com/)
|
1. [Mercedes-Benz Tech Innovation](https://www.mercedes-benz-techinnovation.com/)
|
||||||
1. [Mercedes-Benz.io](https://www.mercedes-benz.io/)
|
|
||||||
1. [Metacore Games](https://metacoregames.com/)
|
|
||||||
1. [Metanet](http://www.metanet.co.kr/en/)
|
1. [Metanet](http://www.metanet.co.kr/en/)
|
||||||
1. [MindSpore](https://mindspore.cn)
|
1. [MindSpore](https://mindspore.cn)
|
||||||
1. [Mirantis](https://mirantis.com/)
|
1. [Mirantis](https://mirantis.com/)
|
||||||
1. [Mission Lane](https://missionlane.com)
|
|
||||||
1. [mixi Group](https://mixi.co.jp/)
|
1. [mixi Group](https://mixi.co.jp/)
|
||||||
1. [Moengage](https://www.moengage.com/)
|
1. [Moengage](https://www.moengage.com/)
|
||||||
1. [Money Forward](https://corp.moneyforward.com/en/)
|
1. [Money Forward](https://corp.moneyforward.com/en/)
|
||||||
1. [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. [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. [Objective](https://www.objective.com.br/)
|
1. [Objective](https://www.objective.com.br/)
|
||||||
1. [OCCMundial](https://occ.com.mx)
|
1. [OCCMundial](https://occ.com.mx)
|
||||||
1. [Octadesk](https://octadesk.com)
|
1. [Octadesk](https://octadesk.com)
|
||||||
1. [Octopus Deploy](https://octopus.com)
|
|
||||||
1. [Olfeo](https://www.olfeo.com/)
|
|
||||||
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. [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. [openLooKeng](https://openlookeng.io)
|
1. [openLooKeng](https://openlookeng.io)
|
||||||
1. [OpenSaaS Studio](https://opensaas.studio)
|
1. [OpenSaaS Studio](https://opensaas.studio)
|
||||||
1. [Opensurvey](https://www.opensurvey.co.kr/)
|
1. [Opensurvey](https://www.opensurvey.co.kr/)
|
||||||
@@ -248,74 +168,44 @@ Currently, the following organizations are **officially** using Argo CD:
|
|||||||
1. [OpsVerse](https://opsverse.io)
|
1. [OpsVerse](https://opsverse.io)
|
||||||
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. [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. [Pandosearch](https://www.pandosearch.com/en/home)
|
1. [Pandosearch](https://www.pandosearch.com/en/home)
|
||||||
|
1. [PagerDuty](https://www.pagerduty.com/)
|
||||||
1. [Patreon](https://www.patreon.com/)
|
1. [Patreon](https://www.patreon.com/)
|
||||||
1. [PayIt](https://payitgov.com/)
|
|
||||||
1. [PayPay](https://paypay.ne.jp/)
|
1. [PayPay](https://paypay.ne.jp/)
|
||||||
1. [Peloton Interactive](https://www.onepeloton.com/)
|
1. [Peloton Interactive](https://www.onepeloton.com/)
|
||||||
1. [Percona](https://percona.com/)
|
|
||||||
1. [PGS](https://www.pgs.com)
|
|
||||||
1. [Pigment](https://www.gopigment.com/)
|
1. [Pigment](https://www.gopigment.com/)
|
||||||
1. [Pipedrive](https://www.pipedrive.com/)
|
|
||||||
1. [Pipefy](https://www.pipefy.com/)
|
1. [Pipefy](https://www.pipefy.com/)
|
||||||
1. [Pipekit](https://pipekit.io/)
|
|
||||||
1. [Pismo](https://pismo.io/)
|
1. [Pismo](https://pismo.io/)
|
||||||
1. [PITS Globale Datenrettungsdienste](https://www.pitsdatenrettung.de/)
|
|
||||||
1. [Platform9 Systems](https://platform9.com/)
|
1. [Platform9 Systems](https://platform9.com/)
|
||||||
1. [Polarpoint.io](https://polarpoint.io)
|
1. [Polarpoint.io](https://polarpoint.io)
|
||||||
1. [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. [Priceline](https://priceline.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. [PUBG](https://www.pubg.com)
|
1. [PUBG](https://www.pubg.com)
|
||||||
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/)
|
||||||
1. [RapidAPI](https://www.rapidapi.com/)
|
1. [RapidAPI](https://www.rapidapi.com/)
|
||||||
1. [rebuy](https://www.rebuy.de/)
|
1. [Recreation.gov](https://www.recreation.gov/)
|
||||||
1. [Red Hat](https://www.redhat.com/)
|
1. [Red Hat](https://www.redhat.com/)
|
||||||
1. [Redpill Linpro](https://www.redpill-linpro.com/)
|
|
||||||
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. [Rise](https://www.risecard.eu/)
|
1. [Rise](https://www.risecard.eu/)
|
||||||
1. [Riskified](https://www.riskified.com/)
|
1. [Riskified](https://www.riskified.com/)
|
||||||
1. [Robotinfra](https://www.robotinfra.com)
|
1. [Robotinfra](https://www.robotinfra.com)
|
||||||
1. [Rocket.Chat](https://rocket.chat)
|
|
||||||
1. [Rogo](https://rogodata.com)
|
|
||||||
1. [Rubin Observatory](https://www.lsst.org)
|
1. [Rubin Observatory](https://www.lsst.org)
|
||||||
1. [Saildrone](https://www.saildrone.com/)
|
1. [Saildrone](https://www.saildrone.com/)
|
||||||
1. [Salad Technologies](https://salad.com/)
|
|
||||||
1. [Saloodo! GmbH](https://www.saloodo.com)
|
1. [Saloodo! GmbH](https://www.saloodo.com)
|
||||||
1. [Sap Labs](http://sap.com)
|
1. [Sap Labs](http://sap.com)
|
||||||
1. [Sauce Labs](https://saucelabs.com/)
|
1. [Sauce Labs](https://saucelabs.com/)
|
||||||
1. [Schwarz IT](https://jobs.schwarz/it-mission)
|
1. [Schwarz IT](https://jobs.schwarz/it-mission)
|
||||||
1. [SCRM Lidl International Hub](https://scrm.lidl)
|
|
||||||
1. [SEEK](https://seek.com.au)
|
|
||||||
1. [SEKAI](https://www.sekai.io/)
|
|
||||||
1. [Semgrep](https://semgrep.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. [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/)
|
||||||
@@ -325,9 +215,6 @@ Currently, the following organizations are **officially** using Argo CD:
|
|||||||
1. [Spendesk](https://spendesk.com/)
|
1. [Spendesk](https://spendesk.com/)
|
||||||
1. [Splunk](https://splunk.com/)
|
1. [Splunk](https://splunk.com/)
|
||||||
1. [Spores Labs](https://spores.app)
|
1. [Spores Labs](https://spores.app)
|
||||||
1. [Statsig](https://statsig.com)
|
|
||||||
1. [SternumIOT](https://sternumiot.com)
|
|
||||||
1. [StreamNative](https://streamnative.io)
|
|
||||||
1. [Stuart](https://stuart.com/)
|
1. [Stuart](https://stuart.com/)
|
||||||
1. [Sumo Logic](https://sumologic.com/)
|
1. [Sumo Logic](https://sumologic.com/)
|
||||||
1. [Sutpc](http://www.sutpc.com/)
|
1. [Sutpc](http://www.sutpc.com/)
|
||||||
@@ -335,17 +222,12 @@ Currently, the following organizations are **officially** using Argo CD:
|
|||||||
1. [Swisscom](https://www.swisscom.ch)
|
1. [Swisscom](https://www.swisscom.ch)
|
||||||
1. [Swissquote](https://github.com/swissquote)
|
1. [Swissquote](https://github.com/swissquote)
|
||||||
1. [Syncier](https://syncier.com/)
|
1. [Syncier](https://syncier.com/)
|
||||||
1. [Syself](https://syself.com)
|
|
||||||
1. [TableCheck](https://tablecheck.com/)
|
1. [TableCheck](https://tablecheck.com/)
|
||||||
1. [Tailor Brands](https://www.tailorbrands.com)
|
1. [Tailor Brands](https://www.tailorbrands.com)
|
||||||
1. [Tamkeen Technologies](https://tamkeentech.sa/)
|
1. [Tamkeen Technologies](https://tamkeentech.sa/)
|
||||||
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. [Tesla](https://tesla.com/)
|
1. [Tesla](https://tesla.com/)
|
||||||
1. [TextNow](https://www.textnow.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)
|
||||||
1. [Tiger Analytics](https://www.tigeranalytics.com/)
|
1. [Tiger Analytics](https://www.tigeranalytics.com/)
|
||||||
@@ -354,22 +236,14 @@ Currently, the following organizations are **officially** using Argo CD:
|
|||||||
1. [Trendyol](https://www.trendyol.com/)
|
1. [Trendyol](https://www.trendyol.com/)
|
||||||
1. [tru.ID](https://tru.id)
|
1. [tru.ID](https://tru.id)
|
||||||
1. [Trusting Social](https://trustingsocial.com/)
|
1. [Trusting Social](https://trustingsocial.com/)
|
||||||
1. [Twilio Segment](https://segment.com/)
|
|
||||||
1. [Twilio SendGrid](https://sendgrid.com)
|
1. [Twilio SendGrid](https://sendgrid.com)
|
||||||
1. [tZERO](https://www.tzero.com/)
|
1. [tZERO](https://www.tzero.com/)
|
||||||
1. [U.S. Veterans Affairs Department](https://www.va.gov/)
|
|
||||||
1. [UBIO](https://ub.io/)
|
1. [UBIO](https://ub.io/)
|
||||||
1. [UFirstGroup](https://www.ufirstgroup.com/en/)
|
1. [UFirstGroup](https://www.ufirstgroup.com/en/)
|
||||||
1. [ungleich.ch](https://ungleich.ch/)
|
1. [ungleich.ch](https://ungleich.ch/)
|
||||||
1. [Unifonic Inc](https://www.unifonic.com/)
|
1. [Unifonic Inc](https://www.unifonic.com/)
|
||||||
1. [Universidad Mesoamericana](https://www.umes.edu.gt/)
|
1. [Universidad Mesoamericana](https://www.umes.edu.gt/)
|
||||||
1. [Upsider Inc.](https://up-sider.com/lp/)
|
|
||||||
1. [Urbantz](https://urbantz.com/)
|
|
||||||
1. [Vectra](https://www.vectra.ai)
|
|
||||||
1. [Veepee](https://www.veepee.com)
|
|
||||||
1. [Verkada](https://www.verkada.com)
|
|
||||||
1. [Viaduct](https://www.viaduct.ai/)
|
1. [Viaduct](https://www.viaduct.ai/)
|
||||||
1. [VietMoney](https://vietmoney.vn/)
|
|
||||||
1. [Vinted](https://vinted.com/)
|
1. [Vinted](https://vinted.com/)
|
||||||
1. [Virtuo](https://www.govirtuo.com/)
|
1. [Virtuo](https://www.govirtuo.com/)
|
||||||
1. [VISITS Technologies](https://visits.world/en)
|
1. [VISITS Technologies](https://visits.world/en)
|
||||||
@@ -390,7 +264,5 @@ Currently, the following organizations are **officially** using Argo CD:
|
|||||||
1. [Yieldlab](https://www.yieldlab.de/)
|
1. [Yieldlab](https://www.yieldlab.de/)
|
||||||
1. [Youverify](https://youverify.co/)
|
1. [Youverify](https://youverify.co/)
|
||||||
1. [Yubo](https://www.yubo.live/)
|
1. [Yubo](https://www.yubo.live/)
|
||||||
1. [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/)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2,9 +2,6 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
@@ -14,43 +11,44 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// clusterSecretEventHandler is used when watching Secrets to check if they are ArgoCD Cluster Secrets, and if so
|
// clusterSecretEventHandler is used when watching Secrets to check if they are ArgoCD Cluster Secrets, and if so
|
||||||
// requeue any related ApplicationSets.
|
// requeue any related ApplicationSets.
|
||||||
type clusterSecretEventHandler struct {
|
type clusterSecretEventHandler struct {
|
||||||
// handler.EnqueueRequestForOwner
|
//handler.EnqueueRequestForOwner
|
||||||
Log log.FieldLogger
|
Log log.FieldLogger
|
||||||
Client client.Client
|
Client client.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *clusterSecretEventHandler) Create(ctx context.Context, e event.CreateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
|
func (h *clusterSecretEventHandler) Create(e event.CreateEvent, q workqueue.RateLimitingInterface) {
|
||||||
h.queueRelatedAppGenerators(ctx, q, e.Object)
|
h.queueRelatedAppGenerators(q, e.Object)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *clusterSecretEventHandler) Update(ctx context.Context, e event.UpdateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
|
func (h *clusterSecretEventHandler) Update(e event.UpdateEvent, q workqueue.RateLimitingInterface) {
|
||||||
h.queueRelatedAppGenerators(ctx, q, e.ObjectNew)
|
h.queueRelatedAppGenerators(q, e.ObjectNew)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *clusterSecretEventHandler) Delete(ctx context.Context, e event.DeleteEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
|
func (h *clusterSecretEventHandler) Delete(e event.DeleteEvent, q workqueue.RateLimitingInterface) {
|
||||||
h.queueRelatedAppGenerators(ctx, q, e.Object)
|
h.queueRelatedAppGenerators(q, e.Object)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *clusterSecretEventHandler) Generic(ctx context.Context, e event.GenericEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
|
func (h *clusterSecretEventHandler) Generic(e event.GenericEvent, q workqueue.RateLimitingInterface) {
|
||||||
h.queueRelatedAppGenerators(ctx, q, e.Object)
|
h.queueRelatedAppGenerators(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(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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +58,7 @@ func (h *clusterSecretEventHandler) queueRelatedAppGenerators(ctx context.Contex
|
|||||||
}).Info("processing event for cluster secret")
|
}).Info("processing event for cluster secret")
|
||||||
|
|
||||||
appSetList := &argoprojiov1alpha1.ApplicationSetList{}
|
appSetList := &argoprojiov1alpha1.ApplicationSetList{}
|
||||||
err := h.Client.List(ctx, appSetList)
|
err := h.Client.List(context.Background(), appSetList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.Log.WithError(err).Error("unable to list ApplicationSets")
|
h.Log.WithError(err).Error("unable to list ApplicationSets")
|
||||||
return
|
return
|
||||||
@@ -68,98 +66,19 @@ func (h *clusterSecretEventHandler) queueRelatedAppGenerators(ctx context.Contex
|
|||||||
|
|
||||||
h.Log.WithField("count", len(appSetList.Items)).Info("listed ApplicationSets")
|
h.Log.WithField("count", len(appSetList.Items)).Info("listed ApplicationSets")
|
||||||
for _, appSet := range appSetList.Items {
|
for _, appSet := range appSetList.Items {
|
||||||
|
|
||||||
foundClusterGenerator := false
|
foundClusterGenerator := false
|
||||||
for _, generator := range appSet.Spec.Generators {
|
for _, generator := range appSet.Spec.Generators {
|
||||||
if generator.Clusters != nil {
|
if generator.Clusters != nil {
|
||||||
foundClusterGenerator = true
|
foundClusterGenerator = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if generator.Matrix != nil {
|
|
||||||
ok, err := nestedGeneratorsHaveClusterGenerator(generator.Matrix.Generators)
|
|
||||||
if err != nil {
|
|
||||||
h.Log.
|
|
||||||
WithFields(log.Fields{
|
|
||||||
"namespace": appSet.GetNamespace(),
|
|
||||||
"name": appSet.GetName(),
|
|
||||||
}).
|
|
||||||
WithError(err).
|
|
||||||
Error("Unable to check if ApplicationSet matrix generators have cluster generator")
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
foundClusterGenerator = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if generator.Merge != nil {
|
|
||||||
ok, err := nestedGeneratorsHaveClusterGenerator(generator.Merge.Generators)
|
|
||||||
if err != nil {
|
|
||||||
h.Log.
|
|
||||||
WithFields(log.Fields{
|
|
||||||
"namespace": appSet.GetNamespace(),
|
|
||||||
"name": appSet.GetName(),
|
|
||||||
}).
|
|
||||||
WithError(err).
|
|
||||||
Error("Unable to check if ApplicationSet merge generators have cluster generator")
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
foundClusterGenerator = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if foundClusterGenerator {
|
if foundClusterGenerator {
|
||||||
|
|
||||||
// TODO: only queue the AppGenerator if the labels match this cluster
|
// TODO: only queue the AppGenerator if the labels match this cluster
|
||||||
req := ctrl.Request{NamespacedName: types.NamespacedName{Namespace: appSet.Namespace, Name: appSet.Name}}
|
req := ctrl.Request{NamespacedName: types.NamespacedName{Namespace: appSet.Namespace, Name: appSet.Name}}
|
||||||
q.Add(req)
|
q.Add(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// nestedGeneratorsHaveClusterGenerator iterate over provided nested generators to check if they have a cluster generator.
|
|
||||||
func nestedGeneratorsHaveClusterGenerator(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator) (bool, error) {
|
|
||||||
for _, generator := range generators {
|
|
||||||
if ok, err := nestedGeneratorHasClusterGenerator(generator); ok || err != nil {
|
|
||||||
return ok, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// nestedGeneratorHasClusterGenerator checks if the provided generator has a cluster generator.
|
|
||||||
func nestedGeneratorHasClusterGenerator(nested argoprojiov1alpha1.ApplicationSetNestedGenerator) (bool, error) {
|
|
||||||
if nested.Clusters != nil {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if nested.Matrix != nil {
|
|
||||||
nestedMatrix, err := argoprojiov1alpha1.ToNestedMatrixGenerator(nested.Matrix)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("unable to get nested matrix generator: %w", err)
|
|
||||||
}
|
|
||||||
if nestedMatrix != nil {
|
|
||||||
hasClusterGenerator, err := nestedGeneratorsHaveClusterGenerator(nestedMatrix.ToMatrixGenerator().Generators)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("error evaluating nested matrix generator: %w", err)
|
|
||||||
}
|
|
||||||
return hasClusterGenerator, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if nested.Merge != nil {
|
|
||||||
nestedMerge, err := argoprojiov1alpha1.ToNestedMergeGenerator(nested.Merge)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("unable to get nested merge generator: %w", err)
|
|
||||||
}
|
|
||||||
if nestedMerge != nil {
|
|
||||||
hasClusterGenerator, err := nestedGeneratorsHaveClusterGenerator(nestedMerge.ToMergeGenerator().Generators)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("error evaluating nested merge generator: %w", err)
|
|
||||||
}
|
|
||||||
return hasClusterGenerator, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"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"
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
|
||||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
@@ -18,16 +13,18 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestClusterEventHandler(t *testing.T) {
|
func TestClusterEventHandler(t *testing.T) {
|
||||||
|
|
||||||
scheme := runtime.NewScheme()
|
scheme := runtime.NewScheme()
|
||||||
err := argov1alpha1.AddToScheme(scheme)
|
err := argov1alpha1.AddToScheme(scheme)
|
||||||
require.NoError(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
err = argov1alpha1.AddToScheme(scheme)
|
err = argov1alpha1.AddToScheme(scheme)
|
||||||
require.NoError(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -43,7 +40,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 +68,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 +111,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 +155,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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -166,6 +163,7 @@ func TestClusterEventHandler(t *testing.T) {
|
|||||||
{NamespacedName: types.NamespacedName{Namespace: "another-namespace", Name: "my-app-set"}},
|
{NamespacedName: types.NamespacedName{Namespace: "another-namespace", Name: "my-app-set"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: "non-argo cd secret should not match",
|
name: "non-argo cd secret should not match",
|
||||||
items: []argov1alpha1.ApplicationSet{
|
items: []argov1alpha1.ApplicationSet{
|
||||||
@@ -191,352 +189,12 @@ func TestClusterEventHandler(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedRequests: []reconcile.Request{},
|
expectedRequests: []reconcile.Request{},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "a matrix generator with a cluster generator should produce a request",
|
|
||||||
items: []argov1alpha1.ApplicationSet{
|
|
||||||
{
|
|
||||||
ObjectMeta: v1.ObjectMeta{
|
|
||||||
Name: "my-app-set",
|
|
||||||
Namespace: "argocd",
|
|
||||||
},
|
|
||||||
Spec: argov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
|
||||||
{
|
|
||||||
Matrix: &argov1alpha1.MatrixGenerator{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
|
|
||||||
{
|
|
||||||
Clusters: &argov1alpha1.ClusterGenerator{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: corev1.Secret{
|
|
||||||
ObjectMeta: v1.ObjectMeta{
|
|
||||||
Namespace: "argocd",
|
|
||||||
Name: "my-secret",
|
|
||||||
Labels: map[string]string{
|
|
||||||
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRequests: []reconcile.Request{{
|
|
||||||
NamespacedName: types.NamespacedName{Namespace: "argocd", Name: "my-app-set"},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "a matrix generator with non cluster generator should not match",
|
|
||||||
items: []argov1alpha1.ApplicationSet{
|
|
||||||
{
|
|
||||||
ObjectMeta: v1.ObjectMeta{
|
|
||||||
Name: "my-app-set",
|
|
||||||
Namespace: "argocd",
|
|
||||||
},
|
|
||||||
Spec: argov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
|
||||||
{
|
|
||||||
Matrix: &argov1alpha1.MatrixGenerator{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
|
|
||||||
{
|
|
||||||
List: &argov1alpha1.ListGenerator{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: corev1.Secret{
|
|
||||||
ObjectMeta: v1.ObjectMeta{
|
|
||||||
Namespace: "argocd",
|
|
||||||
Name: "my-secret",
|
|
||||||
Labels: map[string]string{
|
|
||||||
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRequests: []reconcile.Request{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "a matrix generator with a nested matrix generator containing a cluster generator should produce a request",
|
|
||||||
items: []argov1alpha1.ApplicationSet{
|
|
||||||
{
|
|
||||||
ObjectMeta: v1.ObjectMeta{
|
|
||||||
Name: "my-app-set",
|
|
||||||
Namespace: "argocd",
|
|
||||||
},
|
|
||||||
Spec: argov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
|
||||||
{
|
|
||||||
Matrix: &argov1alpha1.MatrixGenerator{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
|
|
||||||
{
|
|
||||||
Matrix: &apiextensionsv1.JSON{
|
|
||||||
Raw: []byte(
|
|
||||||
`{
|
|
||||||
"generators": [
|
|
||||||
{
|
|
||||||
"clusters": {
|
|
||||||
"selector": {
|
|
||||||
"matchLabels": {
|
|
||||||
"argocd.argoproj.io/secret-type": "cluster"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: corev1.Secret{
|
|
||||||
ObjectMeta: v1.ObjectMeta{
|
|
||||||
Namespace: "argocd",
|
|
||||||
Name: "my-secret",
|
|
||||||
Labels: map[string]string{
|
|
||||||
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRequests: []reconcile.Request{{
|
|
||||||
NamespacedName: types.NamespacedName{Namespace: "argocd", Name: "my-app-set"},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "a matrix generator with a nested matrix generator containing non cluster generator should not match",
|
|
||||||
items: []argov1alpha1.ApplicationSet{
|
|
||||||
{
|
|
||||||
ObjectMeta: v1.ObjectMeta{
|
|
||||||
Name: "my-app-set",
|
|
||||||
Namespace: "argocd",
|
|
||||||
},
|
|
||||||
Spec: argov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
|
||||||
{
|
|
||||||
Matrix: &argov1alpha1.MatrixGenerator{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
|
|
||||||
{
|
|
||||||
Matrix: &apiextensionsv1.JSON{
|
|
||||||
Raw: []byte(
|
|
||||||
`{
|
|
||||||
"generators": [
|
|
||||||
{
|
|
||||||
"list": {
|
|
||||||
"elements": [
|
|
||||||
"a",
|
|
||||||
"b"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: corev1.Secret{
|
|
||||||
ObjectMeta: v1.ObjectMeta{
|
|
||||||
Namespace: "argocd",
|
|
||||||
Name: "my-secret",
|
|
||||||
Labels: map[string]string{
|
|
||||||
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRequests: []reconcile.Request{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "a merge generator with a cluster generator should produce a request",
|
|
||||||
items: []argov1alpha1.ApplicationSet{
|
|
||||||
{
|
|
||||||
ObjectMeta: v1.ObjectMeta{
|
|
||||||
Name: "my-app-set",
|
|
||||||
Namespace: "argocd",
|
|
||||||
},
|
|
||||||
Spec: argov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
|
||||||
{
|
|
||||||
Merge: &argov1alpha1.MergeGenerator{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
|
|
||||||
{
|
|
||||||
Clusters: &argov1alpha1.ClusterGenerator{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: corev1.Secret{
|
|
||||||
ObjectMeta: v1.ObjectMeta{
|
|
||||||
Namespace: "argocd",
|
|
||||||
Name: "my-secret",
|
|
||||||
Labels: map[string]string{
|
|
||||||
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRequests: []reconcile.Request{{
|
|
||||||
NamespacedName: types.NamespacedName{Namespace: "argocd", Name: "my-app-set"},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "a matrix generator with non cluster generator should not match",
|
|
||||||
items: []argov1alpha1.ApplicationSet{
|
|
||||||
{
|
|
||||||
ObjectMeta: v1.ObjectMeta{
|
|
||||||
Name: "my-app-set",
|
|
||||||
Namespace: "argocd",
|
|
||||||
},
|
|
||||||
Spec: argov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
|
||||||
{
|
|
||||||
Merge: &argov1alpha1.MergeGenerator{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
|
|
||||||
{
|
|
||||||
List: &argov1alpha1.ListGenerator{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: corev1.Secret{
|
|
||||||
ObjectMeta: v1.ObjectMeta{
|
|
||||||
Namespace: "argocd",
|
|
||||||
Name: "my-secret",
|
|
||||||
Labels: map[string]string{
|
|
||||||
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRequests: []reconcile.Request{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "a merge generator with a nested merge generator containing a cluster generator should produce a request",
|
|
||||||
items: []argov1alpha1.ApplicationSet{
|
|
||||||
{
|
|
||||||
ObjectMeta: v1.ObjectMeta{
|
|
||||||
Name: "my-app-set",
|
|
||||||
Namespace: "argocd",
|
|
||||||
},
|
|
||||||
Spec: argov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
|
||||||
{
|
|
||||||
Merge: &argov1alpha1.MergeGenerator{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
|
|
||||||
{
|
|
||||||
Merge: &apiextensionsv1.JSON{
|
|
||||||
Raw: []byte(
|
|
||||||
`{
|
|
||||||
"generators": [
|
|
||||||
{
|
|
||||||
"clusters": {
|
|
||||||
"selector": {
|
|
||||||
"matchLabels": {
|
|
||||||
"argocd.argoproj.io/secret-type": "cluster"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: corev1.Secret{
|
|
||||||
ObjectMeta: v1.ObjectMeta{
|
|
||||||
Namespace: "argocd",
|
|
||||||
Name: "my-secret",
|
|
||||||
Labels: map[string]string{
|
|
||||||
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRequests: []reconcile.Request{{
|
|
||||||
NamespacedName: types.NamespacedName{Namespace: "argocd", Name: "my-app-set"},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "a merge generator with a nested merge generator containing non cluster generator should not match",
|
|
||||||
items: []argov1alpha1.ApplicationSet{
|
|
||||||
{
|
|
||||||
ObjectMeta: v1.ObjectMeta{
|
|
||||||
Name: "my-app-set",
|
|
||||||
Namespace: "argocd",
|
|
||||||
},
|
|
||||||
Spec: argov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
|
||||||
{
|
|
||||||
Merge: &argov1alpha1.MergeGenerator{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
|
|
||||||
{
|
|
||||||
Merge: &apiextensionsv1.JSON{
|
|
||||||
Raw: []byte(
|
|
||||||
`{
|
|
||||||
"generators": [
|
|
||||||
{
|
|
||||||
"list": {
|
|
||||||
"elements": [
|
|
||||||
"a",
|
|
||||||
"b"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: corev1.Secret{
|
|
||||||
ObjectMeta: v1.ObjectMeta{
|
|
||||||
Namespace: "argocd",
|
|
||||||
Name: "my-secret",
|
|
||||||
Labels: map[string]string{
|
|
||||||
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRequests: []reconcile.Request{},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
|
||||||
appSetList := argov1alpha1.ApplicationSetList{
|
appSetList := argov1alpha1.ApplicationSetList{
|
||||||
Items: test.items,
|
Items: test.items,
|
||||||
}
|
}
|
||||||
@@ -550,83 +208,26 @@ func TestClusterEventHandler(t *testing.T) {
|
|||||||
|
|
||||||
mockAddRateLimitingInterface := mockAddRateLimitingInterface{}
|
mockAddRateLimitingInterface := mockAddRateLimitingInterface{}
|
||||||
|
|
||||||
handler.queueRelatedAppGenerators(context.Background(), &mockAddRateLimitingInterface, &test.secret)
|
handler.queueRelatedAppGenerators(&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) {
|
|
||||||
nested := argov1alpha1.ApplicationSetNestedGenerator{
|
|
||||||
Clusters: &argov1alpha1.ClusterGenerator{},
|
|
||||||
}
|
|
||||||
|
|
||||||
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.True(t, hasClusterGenerator)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNestedGeneratorHasClusterGenerator_NestedMergeGenerator(t *testing.T) {
|
|
||||||
nested := argov1alpha1.ApplicationSetNestedGenerator{
|
|
||||||
Merge: &apiextensionsv1.JSON{
|
|
||||||
Raw: []byte(
|
|
||||||
`{
|
|
||||||
"generators": [
|
|
||||||
{
|
|
||||||
"clusters": {
|
|
||||||
"selector": {
|
|
||||||
"matchLabels": {
|
|
||||||
"argocd.argoproj.io/secret-type": "cluster"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.True(t, hasClusterGenerator)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNestedGeneratorHasClusterGenerator_NestedMergeGeneratorWithInvalidJSON(t *testing.T) {
|
|
||||||
nested := argov1alpha1.ApplicationSetNestedGenerator{
|
|
||||||
Merge: &apiextensionsv1.JSON{
|
|
||||||
Raw: []byte(
|
|
||||||
`{
|
|
||||||
"generators": [
|
|
||||||
{
|
|
||||||
"clusters": {
|
|
||||||
"selector": {
|
|
||||||
"matchLabels": {
|
|
||||||
"argocd.argoproj.io/secret-type": "cluster"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
|
|
||||||
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.False(t, hasClusterGenerator)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,210 +0,0 @@
|
|||||||
package controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
dynfake "k8s.io/client-go/dynamic/fake"
|
|
||||||
kubefake "k8s.io/client-go/kubernetes/fake"
|
|
||||||
"k8s.io/client-go/tools/record"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
||||||
|
|
||||||
"github.com/argoproj/argo-cd/v2/applicationset/generators"
|
|
||||||
appsetmetrics "github.com/argoproj/argo-cd/v2/applicationset/metrics"
|
|
||||||
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
|
|
||||||
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRequeueAfter(t *testing.T) {
|
|
||||||
mockServer := &mocks.Repos{}
|
|
||||||
ctx := context.Background()
|
|
||||||
scheme := runtime.NewScheme()
|
|
||||||
err := argov1alpha1.AddToScheme(scheme)
|
|
||||||
require.NoError(t, err)
|
|
||||||
gvrToListKind := map[schema.GroupVersionResource]string{{
|
|
||||||
Group: "mallard.io",
|
|
||||||
Version: "v1",
|
|
||||||
Resource: "ducks",
|
|
||||||
}: "DuckList"}
|
|
||||||
appClientset := kubefake.NewSimpleClientset()
|
|
||||||
k8sClient := fake.NewClientBuilder().Build()
|
|
||||||
duckType := &unstructured.Unstructured{
|
|
||||||
Object: map[string]interface{}{
|
|
||||||
"apiVersion": "v2quack",
|
|
||||||
"kind": "Duck",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "mightyduck",
|
|
||||||
"namespace": "namespace",
|
|
||||||
"labels": map[string]interface{}{"duck": "all-species"},
|
|
||||||
},
|
|
||||||
"status": map[string]interface{}{
|
|
||||||
"decisions": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"clusterName": "staging-01",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"clusterName": "production-01",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, duckType)
|
|
||||||
scmConfig := generators.NewSCMConfig("", []string{""}, true, nil, true)
|
|
||||||
terminalGenerators := map[string]generators.Generator{
|
|
||||||
"List": generators.NewListGenerator(),
|
|
||||||
"Clusters": generators.NewClusterGenerator(k8sClient, ctx, appClientset, "argocd"),
|
|
||||||
"Git": generators.NewGitGenerator(mockServer, "namespace"),
|
|
||||||
"SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), scmConfig),
|
|
||||||
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, fakeDynClient, appClientset, "argocd"),
|
|
||||||
"PullRequest": generators.NewPullRequestGenerator(k8sClient, scmConfig),
|
|
||||||
}
|
|
||||||
|
|
||||||
nestedGenerators := map[string]generators.Generator{
|
|
||||||
"List": terminalGenerators["List"],
|
|
||||||
"Clusters": terminalGenerators["Clusters"],
|
|
||||||
"Git": terminalGenerators["Git"],
|
|
||||||
"SCMProvider": terminalGenerators["SCMProvider"],
|
|
||||||
"ClusterDecisionResource": terminalGenerators["ClusterDecisionResource"],
|
|
||||||
"PullRequest": terminalGenerators["PullRequest"],
|
|
||||||
"Matrix": generators.NewMatrixGenerator(terminalGenerators),
|
|
||||||
"Merge": generators.NewMergeGenerator(terminalGenerators),
|
|
||||||
}
|
|
||||||
|
|
||||||
topLevelGenerators := map[string]generators.Generator{
|
|
||||||
"List": terminalGenerators["List"],
|
|
||||||
"Clusters": terminalGenerators["Clusters"],
|
|
||||||
"Git": terminalGenerators["Git"],
|
|
||||||
"SCMProvider": terminalGenerators["SCMProvider"],
|
|
||||||
"ClusterDecisionResource": terminalGenerators["ClusterDecisionResource"],
|
|
||||||
"PullRequest": terminalGenerators["PullRequest"],
|
|
||||||
"Matrix": generators.NewMatrixGenerator(nestedGenerators),
|
|
||||||
"Merge": generators.NewMergeGenerator(nestedGenerators),
|
|
||||||
}
|
|
||||||
|
|
||||||
client := fake.NewClientBuilder().WithScheme(scheme).Build()
|
|
||||||
metrics := appsetmetrics.NewFakeAppsetMetrics(client)
|
|
||||||
r := ApplicationSetReconciler{
|
|
||||||
Client: client,
|
|
||||||
Scheme: scheme,
|
|
||||||
Recorder: record.NewFakeRecorder(0),
|
|
||||||
Generators: topLevelGenerators,
|
|
||||||
Metrics: metrics,
|
|
||||||
}
|
|
||||||
|
|
||||||
type args struct {
|
|
||||||
appset *argov1alpha1.ApplicationSet
|
|
||||||
requeueAfterOverride string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want time.Duration
|
|
||||||
wantErr assert.ErrorAssertionFunc
|
|
||||||
}{
|
|
||||||
{name: "Cluster", args: args{
|
|
||||||
appset: &argov1alpha1.ApplicationSet{
|
|
||||||
Spec: argov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetGenerator{{Clusters: &argov1alpha1.ClusterGenerator{}}},
|
|
||||||
},
|
|
||||||
}, requeueAfterOverride: "",
|
|
||||||
}, want: generators.NoRequeueAfter, wantErr: assert.NoError},
|
|
||||||
{name: "ClusterMergeNested", 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{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, ""}, want: generators.DefaultRequeueAfterSeconds, wantErr: assert.NoError},
|
|
||||||
{name: "ClusterMatrixNested", args: args{&argov1alpha1.ApplicationSet{
|
|
||||||
Spec: argov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
|
||||||
{Clusters: &argov1alpha1.ClusterGenerator{}},
|
|
||||||
{Matrix: &argov1alpha1.MatrixGenerator{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
|
|
||||||
{
|
|
||||||
Clusters: &argov1alpha1.ClusterGenerator{},
|
|
||||||
Git: &argov1alpha1.GitGenerator{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, ""}, want: generators.DefaultRequeueAfterSeconds, wantErr: assert.NoError},
|
|
||||||
{name: "ListGenerator", args: args{appset: &argov1alpha1.ApplicationSet{
|
|
||||||
Spec: argov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argov1alpha1.ApplicationSetGenerator{{List: &argov1alpha1.ListGenerator{}}},
|
|
||||||
},
|
|
||||||
}}, 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 {
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package template
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
|
||||||
|
|
||||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
|
||||||
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func applyTemplatePatch(app *appv1.Application, templatePatch string) (*appv1.Application, error) {
|
|
||||||
appString, err := json.Marshal(app)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error while marhsalling Application %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
convertedTemplatePatch, err := utils.ConvertYAMLToJSON(templatePatch)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error while converting template to json %q: %w", convertedTemplatePatch, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(convertedTemplatePatch), &appv1.Application{}); err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid templatePatch %q: %w", convertedTemplatePatch, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := strategicpatch.StrategicMergePatch(appString, []byte(convertedTemplatePatch), appv1.Application{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error while applying templatePatch template to json %q: %w", convertedTemplatePatch, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
finalApp := appv1.Application{}
|
|
||||||
err = json.Unmarshal(data, &finalApp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error while unmarhsalling patched application: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent changes to the `project` field. This helps prevent malicious template patches
|
|
||||||
finalApp.Spec.Project = app.Spec.Project
|
|
||||||
|
|
||||||
return &finalApp, nil
|
|
||||||
}
|
|
||||||
@@ -1,249 +0,0 @@
|
|||||||
package template
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
|
|
||||||
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_ApplyTemplatePatch(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
appTemplate *appv1.Application
|
|
||||||
templatePatch string
|
|
||||||
expectedApp *appv1.Application
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "patch with JSON",
|
|
||||||
appTemplate: &appv1.Application{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
Kind: "Application",
|
|
||||||
APIVersion: "argoproj.io/v1alpha1",
|
|
||||||
},
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "my-cluster-guestbook",
|
|
||||||
Namespace: "namespace",
|
|
||||||
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
|
|
||||||
},
|
|
||||||
Spec: appv1.ApplicationSpec{
|
|
||||||
Project: "default",
|
|
||||||
Source: &appv1.ApplicationSource{
|
|
||||||
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
|
|
||||||
TargetRevision: "HEAD",
|
|
||||||
Path: "guestbook",
|
|
||||||
},
|
|
||||||
Destination: appv1.ApplicationDestination{
|
|
||||||
Server: "https://kubernetes.default.svc",
|
|
||||||
Namespace: "guestbook",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
templatePatch: `{
|
|
||||||
"metadata": {
|
|
||||||
"annotations": {
|
|
||||||
"annotation-some-key": "annotation-some-value"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"source": {
|
|
||||||
"helm": {
|
|
||||||
"valueFiles": [
|
|
||||||
"values.test.yaml",
|
|
||||||
"values.big.yaml"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"syncPolicy": {
|
|
||||||
"automated": {
|
|
||||||
"prune": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
expectedApp: &appv1.Application{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
Kind: "Application",
|
|
||||||
APIVersion: "argoproj.io/v1alpha1",
|
|
||||||
},
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "my-cluster-guestbook",
|
|
||||||
Namespace: "namespace",
|
|
||||||
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
|
|
||||||
Annotations: map[string]string{
|
|
||||||
"annotation-some-key": "annotation-some-value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Spec: appv1.ApplicationSpec{
|
|
||||||
Project: "default",
|
|
||||||
Source: &appv1.ApplicationSource{
|
|
||||||
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
|
|
||||||
TargetRevision: "HEAD",
|
|
||||||
Path: "guestbook",
|
|
||||||
Helm: &appv1.ApplicationSourceHelm{
|
|
||||||
ValueFiles: []string{
|
|
||||||
"values.test.yaml",
|
|
||||||
"values.big.yaml",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Destination: appv1.ApplicationDestination{
|
|
||||||
Server: "https://kubernetes.default.svc",
|
|
||||||
Namespace: "guestbook",
|
|
||||||
},
|
|
||||||
SyncPolicy: &appv1.SyncPolicy{
|
|
||||||
Automated: &appv1.SyncPolicyAutomated{
|
|
||||||
Prune: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "patch with YAML",
|
|
||||||
appTemplate: &appv1.Application{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
Kind: "Application",
|
|
||||||
APIVersion: "argoproj.io/v1alpha1",
|
|
||||||
},
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "my-cluster-guestbook",
|
|
||||||
Namespace: "namespace",
|
|
||||||
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
|
|
||||||
},
|
|
||||||
Spec: appv1.ApplicationSpec{
|
|
||||||
Project: "default",
|
|
||||||
Source: &appv1.ApplicationSource{
|
|
||||||
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
|
|
||||||
TargetRevision: "HEAD",
|
|
||||||
Path: "guestbook",
|
|
||||||
},
|
|
||||||
Destination: appv1.ApplicationDestination{
|
|
||||||
Server: "https://kubernetes.default.svc",
|
|
||||||
Namespace: "guestbook",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
templatePatch: `
|
|
||||||
metadata:
|
|
||||||
annotations:
|
|
||||||
annotation-some-key: annotation-some-value
|
|
||||||
spec:
|
|
||||||
source:
|
|
||||||
helm:
|
|
||||||
valueFiles:
|
|
||||||
- values.test.yaml
|
|
||||||
- values.big.yaml
|
|
||||||
syncPolicy:
|
|
||||||
automated:
|
|
||||||
prune: true`,
|
|
||||||
expectedApp: &appv1.Application{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
Kind: "Application",
|
|
||||||
APIVersion: "argoproj.io/v1alpha1",
|
|
||||||
},
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "my-cluster-guestbook",
|
|
||||||
Namespace: "namespace",
|
|
||||||
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
|
|
||||||
Annotations: map[string]string{
|
|
||||||
"annotation-some-key": "annotation-some-value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Spec: appv1.ApplicationSpec{
|
|
||||||
Project: "default",
|
|
||||||
Source: &appv1.ApplicationSource{
|
|
||||||
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
|
|
||||||
TargetRevision: "HEAD",
|
|
||||||
Path: "guestbook",
|
|
||||||
Helm: &appv1.ApplicationSourceHelm{
|
|
||||||
ValueFiles: []string{
|
|
||||||
"values.test.yaml",
|
|
||||||
"values.big.yaml",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Destination: appv1.ApplicationDestination{
|
|
||||||
Server: "https://kubernetes.default.svc",
|
|
||||||
Namespace: "guestbook",
|
|
||||||
},
|
|
||||||
SyncPolicy: &appv1.SyncPolicy{
|
|
||||||
Automated: &appv1.SyncPolicyAutomated{
|
|
||||||
Prune: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "project field isn't overwritten",
|
|
||||||
appTemplate: &appv1.Application{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
Kind: "Application",
|
|
||||||
APIVersion: "argoproj.io/v1alpha1",
|
|
||||||
},
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "my-cluster-guestbook",
|
|
||||||
Namespace: "namespace",
|
|
||||||
},
|
|
||||||
Spec: appv1.ApplicationSpec{
|
|
||||||
Project: "default",
|
|
||||||
Source: &appv1.ApplicationSource{
|
|
||||||
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
|
|
||||||
TargetRevision: "HEAD",
|
|
||||||
Path: "guestbook",
|
|
||||||
},
|
|
||||||
Destination: appv1.ApplicationDestination{
|
|
||||||
Server: "https://kubernetes.default.svc",
|
|
||||||
Namespace: "guestbook",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
templatePatch: `
|
|
||||||
spec:
|
|
||||||
project: my-project`,
|
|
||||||
expectedApp: &appv1.Application{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
Kind: "Application",
|
|
||||||
APIVersion: "argoproj.io/v1alpha1",
|
|
||||||
},
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "my-cluster-guestbook",
|
|
||||||
Namespace: "namespace",
|
|
||||||
},
|
|
||||||
Spec: appv1.ApplicationSpec{
|
|
||||||
Project: "default",
|
|
||||||
Source: &appv1.ApplicationSource{
|
|
||||||
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
|
|
||||||
TargetRevision: "HEAD",
|
|
||||||
Path: "guestbook",
|
|
||||||
},
|
|
||||||
Destination: appv1.ApplicationDestination{
|
|
||||||
Server: "https://kubernetes.default.svc",
|
|
||||||
Namespace: "guestbook",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
tcc := tc
|
|
||||||
t.Run(tcc.name, func(t *testing.T) {
|
|
||||||
result, err := applyTemplatePatch(tcc.appTemplate, tcc.templatePatch)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, *tcc.expectedApp, *result)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestError(t *testing.T) {
|
|
||||||
app := &appv1.Application{}
|
|
||||||
|
|
||||||
result, err := applyTemplatePatch(app, "hello world")
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Nil(t, result)
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
apiVersion: argoproj.io/v1alpha1
|
|
||||||
kind: ApplicationSet
|
|
||||||
metadata:
|
|
||||||
name: guestbook
|
|
||||||
spec:
|
|
||||||
goTemplate: true
|
|
||||||
generators:
|
|
||||||
- list:
|
|
||||||
elements:
|
|
||||||
- cluster: engineering-dev
|
|
||||||
url: https://kubernetes.default.svc
|
|
||||||
foo: bar
|
|
||||||
# Update foo value with foo: bar
|
|
||||||
# Application engineering-prod-guestbook labels will still be baz
|
|
||||||
# Delete this element
|
|
||||||
# Application engineering-prod-guestbook will be kept
|
|
||||||
- cluster: engineering-prod
|
|
||||||
url: https://kubernetes.default.svc
|
|
||||||
foo: baz
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
name: '{{.cluster}}-guestbook'
|
|
||||||
labels:
|
|
||||||
foo: '{{.foo}}'
|
|
||||||
spec:
|
|
||||||
project: default
|
|
||||||
source:
|
|
||||||
repoURL: https://github.com/argoproj/argo-cd.git
|
|
||||||
targetRevision: HEAD
|
|
||||||
path: applicationset/examples/list-generator/guestbook/{{.cluster}}
|
|
||||||
destination:
|
|
||||||
server: '{{.url}}'
|
|
||||||
namespace: guestbook
|
|
||||||
syncPolicy:
|
|
||||||
applicationsSync: create-only
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
apiVersion: argoproj.io/v1alpha1
|
|
||||||
kind: ApplicationSet
|
|
||||||
metadata:
|
|
||||||
name: guestbook
|
|
||||||
spec:
|
|
||||||
goTemplate: true
|
|
||||||
generators:
|
|
||||||
- list:
|
|
||||||
elements:
|
|
||||||
- cluster: engineering-dev
|
|
||||||
url: https://kubernetes.default.svc
|
|
||||||
foo: bar
|
|
||||||
# Update foo value with foo: bar
|
|
||||||
# Application engineering-prod-guestbook labels will change to foo: bar
|
|
||||||
# Delete this element
|
|
||||||
# Application engineering-prod-guestbook will be kept
|
|
||||||
- cluster: engineering-prod
|
|
||||||
url: https://kubernetes.default.svc
|
|
||||||
foo: baz
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
name: '{{.cluster}}-guestbook'
|
|
||||||
labels:
|
|
||||||
foo: '{{.foo}}'
|
|
||||||
spec:
|
|
||||||
project: default
|
|
||||||
source:
|
|
||||||
repoURL: https://github.com/argoproj/argo-cd.git
|
|
||||||
targetRevision: HEAD
|
|
||||||
path: applicationset/examples/list-generator/guestbook/{{.cluster}}
|
|
||||||
destination:
|
|
||||||
server: '{{.url}}'
|
|
||||||
namespace: guestbook
|
|
||||||
syncPolicy:
|
|
||||||
applicationsSync: create-update
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: guestbook-ui
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
revisionHistoryLimit: 3
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: guestbook-ui
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: guestbook-ui
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
|
|
||||||
name: guestbook-ui
|
|
||||||
ports:
|
|
||||||
- containerPort: 80
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: guestbook-ui
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
targetPort: 80
|
|
||||||
selector:
|
|
||||||
app: guestbook-ui
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: guestbook-ui
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
revisionHistoryLimit: 3
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: guestbook-ui
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: guestbook-ui
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
|
|
||||||
name: guestbook-ui
|
|
||||||
ports:
|
|
||||||
- containerPort: 80
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: guestbook-ui
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
targetPort: 80
|
|
||||||
selector:
|
|
||||||
app: guestbook-ui
|
|
||||||
@@ -4,7 +4,6 @@ metadata:
|
|||||||
name: guestbook
|
name: guestbook
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- clusters: {}
|
- clusters: {}
|
||||||
template:
|
template:
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ metadata:
|
|||||||
name: book-import
|
name: book-import
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- clusterDecisionResource:
|
- clusterDecisionResource:
|
||||||
configMapRef: ocm-placement
|
configMapRef: ocm-placement
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ metadata:
|
|||||||
name: guestbook
|
name: guestbook
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- clusters: {}
|
- clusters: {}
|
||||||
template:
|
template:
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ metadata:
|
|||||||
name: cluster-addons
|
name: cluster-addons
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- git:
|
- git:
|
||||||
repoURL: https://github.com/infra-team/cluster-deployments.git
|
repoURL: https://github.com/infra-team/cluster-deployments.git
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ metadata:
|
|||||||
name: guestbook
|
name: guestbook
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- git:
|
- git:
|
||||||
repoURL: https://github.com/infra-team/cluster-deployments.git
|
repoURL: https://github.com/infra-team/cluster-deployments.git
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ metadata:
|
|||||||
name: guestbook
|
name: guestbook
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- git:
|
- git:
|
||||||
repoURL: https://github.com/infra-team/cluster-deployments.git
|
repoURL: https://github.com/infra-team/cluster-deployments.git
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ metadata:
|
|||||||
name: guestbook
|
name: guestbook
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- list:
|
- list:
|
||||||
elements:
|
elements:
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ metadata:
|
|||||||
name: guestbook
|
name: guestbook
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- list:
|
- list:
|
||||||
elements:
|
elements:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ metadata:
|
|||||||
namespace: argocd
|
namespace: argocd
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- git:
|
- git:
|
||||||
repoURL: https://github.com/argoproj/argo-cd.git
|
repoURL: https://github.com/argoproj/argo-cd.git
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ metadata:
|
|||||||
namespace: argocd
|
namespace: argocd
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- git:
|
- git:
|
||||||
repoURL: https://github.com/argoproj/argo-cd.git
|
repoURL: https://github.com/argoproj/argo-cd.git
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ metadata:
|
|||||||
name: guestbook
|
name: guestbook
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- git:
|
- git:
|
||||||
repoURL: https://github.com/argoproj/argo-cd.git
|
repoURL: https://github.com/argoproj/argo-cd.git
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
key:
|
|
||||||
components:
|
|
||||||
- name: component1
|
|
||||||
chart: podinfo
|
|
||||||
version: "6.3.2"
|
|
||||||
releaseName: component1
|
|
||||||
repoUrl: "https://stefanprodan.github.io/podinfo"
|
|
||||||
namespace: component1
|
|
||||||
- name: component2
|
|
||||||
chart: podinfo
|
|
||||||
version: "6.3.3"
|
|
||||||
releaseName: component2
|
|
||||||
repoUrl: "ghcr.io/stefanprodan/charts"
|
|
||||||
namespace: component2
|
|
||||||
@@ -4,7 +4,6 @@ metadata:
|
|||||||
name: guestbook
|
name: guestbook
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- list:
|
- list:
|
||||||
elements:
|
elements:
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ metadata:
|
|||||||
name: cluster-git
|
name: cluster-git
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- matrix:
|
- matrix:
|
||||||
generators:
|
generators:
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ metadata:
|
|||||||
name: list-git
|
name: list-git
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- matrix:
|
- matrix:
|
||||||
generators:
|
generators:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ metadata:
|
|||||||
namespace: argocd
|
namespace: argocd
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- matrix:
|
- matrix:
|
||||||
generators:
|
generators:
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ metadata:
|
|||||||
name: matrix-and-union-in-matrix
|
name: matrix-and-union-in-matrix
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- matrix:
|
- matrix:
|
||||||
generators:
|
generators:
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ metadata:
|
|||||||
name: merge-clusters-and-list
|
name: merge-clusters-and-list
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- merge:
|
- merge:
|
||||||
mergeKeys:
|
mergeKeys:
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ metadata:
|
|||||||
name: merge-two-matrixes
|
name: merge-two-matrixes
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- merge:
|
- merge:
|
||||||
mergeKeys:
|
mergeKeys:
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ metadata:
|
|||||||
name: myapp
|
name: myapp
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- pullRequest:
|
- pullRequest:
|
||||||
github:
|
github:
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
apiVersion: argoproj.io/v1alpha1
|
|
||||||
kind: ApplicationSet
|
|
||||||
metadata:
|
|
||||||
name: guestbook
|
|
||||||
spec:
|
|
||||||
generators:
|
|
||||||
- scmProvider:
|
|
||||||
gitlab:
|
|
||||||
api: https://gitlab.com
|
|
||||||
group: test-argocd-proton
|
|
||||||
includeSubgroups: true
|
|
||||||
cloneProtocol: https
|
|
||||||
filters:
|
|
||||||
- repositoryMatch: test-app
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
name: '{{ repository }}-guestbook'
|
|
||||||
spec:
|
|
||||||
project: "default"
|
|
||||||
source:
|
|
||||||
repoURL: '{{ url }}'
|
|
||||||
targetRevision: '{{ branch }}'
|
|
||||||
path: guestbook
|
|
||||||
destination:
|
|
||||||
server: https://kubernetes.default.svc
|
|
||||||
namespace: guestbook
|
|
||||||
@@ -4,7 +4,6 @@ metadata:
|
|||||||
name: guestbook
|
name: guestbook
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- scmProvider:
|
- scmProvider:
|
||||||
github:
|
github:
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ metadata:
|
|||||||
name: guestbook
|
name: guestbook
|
||||||
spec:
|
spec:
|
||||||
goTemplate: true
|
goTemplate: true
|
||||||
goTemplateOptions: ["missingkey=error"]
|
|
||||||
generators:
|
generators:
|
||||||
- list:
|
- list:
|
||||||
elements:
|
elements:
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -34,6 +38,7 @@ type ClusterGenerator struct {
|
|||||||
var render = &utils.Render{}
|
var render = &utils.Render{}
|
||||||
|
|
||||||
func NewClusterGenerator(c client.Client, ctx context.Context, clientset kubernetes.Interface, namespace string) Generator {
|
func NewClusterGenerator(c client.Client, ctx context.Context, clientset kubernetes.Interface, namespace string) Generator {
|
||||||
|
|
||||||
settingsManager := settings.NewSettingsManager(ctx, clientset, namespace)
|
settingsManager := settings.NewSettingsManager(ctx, clientset, namespace)
|
||||||
|
|
||||||
g := &ClusterGenerator{
|
g := &ClusterGenerator{
|
||||||
@@ -46,9 +51,7 @@ func NewClusterGenerator(c client.Client, ctx context.Context, clientset kuberne
|
|||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRequeueAfter never requeue the cluster generator because the `clusterSecretEventHandler` will requeue the appsets
|
func (g *ClusterGenerator) GetRequeueAfter(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) time.Duration {
|
||||||
// when the cluster secrets change
|
|
||||||
func (g *ClusterGenerator) GetRequeueAfter(_ *argoappsetv1alpha1.ApplicationSetGenerator) time.Duration {
|
|
||||||
return NoRequeueAfter
|
return NoRequeueAfter
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,8 +59,9 @@ func (g *ClusterGenerator) GetTemplate(appSetGenerator *argoappsetv1alpha1.Appli
|
|||||||
return &appSetGenerator.Clusters.Template
|
return &appSetGenerator.Clusters.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
|
func (g *ClusterGenerator) GenerateParams(
|
||||||
logCtx := log.WithField("applicationset", appSet.GetName()).WithField("namespace", appSet.GetNamespace())
|
appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
|
||||||
|
|
||||||
if appSetGenerator == nil {
|
if appSetGenerator == nil {
|
||||||
return nil, EmptyAppSetGeneratorError
|
return nil, EmptyAppSetGeneratorError
|
||||||
}
|
}
|
||||||
@@ -73,51 +77,44 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
|
|||||||
// ListCluster from Argo CD's util/db package will include the local cluster in the list of clusters
|
// ListCluster from Argo CD's util/db package will include the local cluster in the list of clusters
|
||||||
clustersFromArgoCD, err := utils.ListClusters(g.ctx, g.clientset, g.namespace)
|
clustersFromArgoCD, err := utils.ListClusters(g.ctx, g.clientset, g.namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error listing clusters: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if clustersFromArgoCD == nil {
|
if clustersFromArgoCD == nil {
|
||||||
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.
|
||||||
if secretForCluster, exists := clusterSecrets[cluster.Name]; exists {
|
if secretForCluster, exists := clusterSecrets[cluster.Name]; exists {
|
||||||
secretsFound = append(secretsFound, secretForCluster)
|
secretsFound = append(secretsFound, secretForCluster)
|
||||||
|
|
||||||
} else if !ignoreLocalClusters {
|
} else if !ignoreLocalClusters {
|
||||||
// If there is no secret for the cluster, it's the local cluster, so handle it here.
|
// If there is no secret for the cluster, it's the local cluster, so handle it here.
|
||||||
params := map[string]interface{}{}
|
params := map[string]interface{}{}
|
||||||
params["name"] = cluster.Name
|
params["name"] = cluster.Name
|
||||||
params["nameNormalized"] = cluster.Name
|
params["nameNormalized"] = cluster.Name
|
||||||
params["server"] = cluster.Server
|
params["server"] = cluster.Server
|
||||||
params["project"] = ""
|
|
||||||
|
|
||||||
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
|
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet)
|
||||||
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 +126,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{}{}
|
||||||
|
|
||||||
@@ -157,41 +147,71 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
|
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet)
|
||||||
if err != nil {
|
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 appendTemplatedValues(clusterValues map[string]string, params map[string]interface{}, appSet *argoappsetv1alpha1.ApplicationSet) error {
|
||||||
|
// We create a local map to ensure that we do not fall victim to a billion-laughs attack. We iterate through the
|
||||||
|
// cluster values map and only replace values in said map if it has already been whitelisted in the params map.
|
||||||
|
// Once we iterate through all the cluster values we can then safely merge the `tmp` map into the main params map.
|
||||||
|
tmp := map[string]interface{}{}
|
||||||
|
|
||||||
|
for key, value := range clusterValues {
|
||||||
|
result, err := replaceTemplatedString(value, params, appSet)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error replacing templated String: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if appSet.Spec.GoTemplate {
|
||||||
|
if tmp["values"] == nil {
|
||||||
|
tmp["values"] = map[string]string{}
|
||||||
|
}
|
||||||
|
tmp["values"].(map[string]string)[key] = result
|
||||||
|
} else {
|
||||||
|
tmp[fmt.Sprintf("values.%s", key)] = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range tmp {
|
||||||
|
params[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceTemplatedString(value string, params map[string]interface{}, appSet *argoappsetv1alpha1.ApplicationSet) (string, error) {
|
||||||
|
replacedTmplStr, err := render.Replace(value, params, appSet.Spec.GoTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return replacedTmplStr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *ClusterGenerator) getSecretsByClusterName(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) (map[string]corev1.Secret, error) {
|
||||||
|
// List all Clusters:
|
||||||
clusterSecretList := &corev1.SecretList{}
|
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{}
|
||||||
|
|
||||||
@@ -202,4 +222,5 @@ func (g *ClusterGenerator) getSecretsByClusterName(log *log.Entry, appSetGenerat
|
|||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type possiblyErroringFakeCtrlRuntimeClient struct {
|
type possiblyErroringFakeCtrlRuntimeClient struct {
|
||||||
@@ -76,20 +75,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 +104,13 @@ 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",
|
||||||
{
|
"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": "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",
|
||||||
"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"},
|
||||||
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
|
|
||||||
},
|
{"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "{{ metadata.annotations.foo.argoproj.io }}", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "{{ metadata.labels.environment }}", "values.aaa": "https://kubernetes.default.svc", "nameNormalized": "in-cluster", "name": "in-cluster", "server": "https://kubernetes.default.svc"},
|
||||||
},
|
},
|
||||||
clientError: false,
|
clientError: false,
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
@@ -130,15 +124,11 @@ func TestGenerateParams(t *testing.T) {
|
|||||||
},
|
},
|
||||||
values: nil,
|
values: nil,
|
||||||
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"},
|
||||||
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{"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"},
|
||||||
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
clientError: false,
|
clientError: false,
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
@@ -154,10 +144,8 @@ func TestGenerateParams(t *testing.T) {
|
|||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
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"},
|
||||||
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
clientError: false,
|
clientError: false,
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
@@ -180,14 +168,10 @@ func TestGenerateParams(t *testing.T) {
|
|||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
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"},
|
||||||
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
|
{"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"},
|
||||||
{
|
|
||||||
"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",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
clientError: false,
|
clientError: false,
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
@@ -213,10 +197,8 @@ func TestGenerateParams(t *testing.T) {
|
|||||||
"name": "baz",
|
"name": "baz",
|
||||||
},
|
},
|
||||||
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"},
|
||||||
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
clientError: false,
|
clientError: false,
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
@@ -227,75 +209,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,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +220,9 @@ func TestGenerateParams(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
|
||||||
appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
|
appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
|
||||||
|
|
||||||
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
|
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
|
||||||
@@ -315,7 +231,7 @@ func TestGenerateParams(t *testing.T) {
|
|||||||
testCase.clientError,
|
testCase.clientError,
|
||||||
}
|
}
|
||||||
|
|
||||||
clusterGenerator := NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
|
var clusterGenerator = NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
|
||||||
|
|
||||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
@@ -328,16 +244,16 @@ 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)
|
||||||
|
|
||||||
if testCase.expectedError != nil {
|
if testCase.expectedError != nil {
|
||||||
require.EqualError(t, err, testCase.expectedError.Error())
|
assert.EqualError(t, err, testCase.expectedError.Error())
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.ElementsMatch(t, testCase.expected, got)
|
assert.ElementsMatch(t, testCase.expected, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -394,11 +310,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 +335,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 +360,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 +385,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 +413,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 +428,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 +458,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 +498,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 +516,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 +559,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 +583,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,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -844,7 +594,9 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
|
||||||
appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
|
appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
|
||||||
|
|
||||||
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
|
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
|
||||||
@@ -853,7 +605,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
|
|||||||
testCase.clientError,
|
testCase.clientError,
|
||||||
}
|
}
|
||||||
|
|
||||||
clusterGenerator := NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
|
var clusterGenerator = NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
|
||||||
|
|
||||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
@@ -868,16 +620,16 @@ 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)
|
||||||
|
|
||||||
if testCase.expectedError != nil {
|
if testCase.expectedError != nil {
|
||||||
require.EqualError(t, err, testCase.expectedError.Error())
|
assert.EqualError(t, err, testCase.expectedError.Error())
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.ElementsMatch(t, testCase.expected, got)
|
assert.ElementsMatch(t, testCase.expected, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
|
|
||||||
"github.com/argoproj/argo-cd/v2/util/settings"
|
"github.com/argoproj/argo-cd/v2/util/settings"
|
||||||
|
|
||||||
@@ -33,6 +32,7 @@ type DuckTypeGenerator struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewDuckTypeGenerator(ctx context.Context, dynClient dynamic.Interface, clientset kubernetes.Interface, namespace string) Generator {
|
func NewDuckTypeGenerator(ctx context.Context, dynClient dynamic.Interface, clientset kubernetes.Interface, namespace string) Generator {
|
||||||
|
|
||||||
settingsManager := settings.NewSettingsManager(ctx, clientset, namespace)
|
settingsManager := settings.NewSettingsManager(ctx, clientset, namespace)
|
||||||
|
|
||||||
g := &DuckTypeGenerator{
|
g := &DuckTypeGenerator{
|
||||||
@@ -46,20 +46,22 @@ func NewDuckTypeGenerator(ctx context.Context, dynClient dynamic.Interface, clie
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *DuckTypeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
|
func (g *DuckTypeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
|
||||||
|
|
||||||
// Return a requeue default of 3 minutes, if no override is specified.
|
// Return a requeue default of 3 minutes, if no override is specified.
|
||||||
|
|
||||||
if appSetGenerator.ClusterDecisionResource.RequeueAfterSeconds != nil {
|
if appSetGenerator.ClusterDecisionResource.RequeueAfterSeconds != nil {
|
||||||
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 {
|
||||||
return &appSetGenerator.ClusterDecisionResource.Template
|
return &appSetGenerator.ClusterDecisionResource.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
|
func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
|
||||||
|
|
||||||
if appSetGenerator == nil {
|
if appSetGenerator == nil {
|
||||||
return nil, EmptyAppSetGeneratorError
|
return nil, EmptyAppSetGeneratorError
|
||||||
}
|
}
|
||||||
@@ -72,7 +74,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
|
|||||||
// ListCluster from Argo CD's util/db package will include the local cluster in the list of clusters
|
// ListCluster from Argo CD's util/db package will include the local cluster in the list of clusters
|
||||||
clustersFromArgoCD, err := utils.ListClusters(g.ctx, g.clientset, g.namespace)
|
clustersFromArgoCD, err := utils.ListClusters(g.ctx, g.clientset, g.namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error listing clusters: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if clustersFromArgoCD == nil {
|
if clustersFromArgoCD == nil {
|
||||||
@@ -81,8 +83,9 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
|
|||||||
|
|
||||||
// Read the configMapRef
|
// Read the configMapRef
|
||||||
cm, err := g.clientset.CoreV1().ConfigMaps(g.namespace).Get(g.ctx, appSetGenerator.ClusterDecisionResource.ConfigMapRef, metav1.GetOptions{})
|
cm, err := g.clientset.CoreV1().ConfigMaps(g.namespace).Get(g.ctx, appSetGenerator.ClusterDecisionResource.ConfigMapRef, metav1.GetOptions{})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error reading configMapRef: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract GVK data for the dynamic client to use
|
// Extract GVK data for the dynamic client to use
|
||||||
@@ -101,6 +104,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
|
|||||||
|
|
||||||
if (resourceName == "" && labelSelector.MatchLabels == nil && labelSelector.MatchExpressions == nil) ||
|
if (resourceName == "" && labelSelector.MatchLabels == nil && labelSelector.MatchExpressions == nil) ||
|
||||||
(resourceName != "" && (labelSelector.MatchExpressions != nil || labelSelector.MatchLabels != nil)) {
|
(resourceName != "" && (labelSelector.MatchExpressions != nil || labelSelector.MatchLabels != nil)) {
|
||||||
|
|
||||||
log.Warningf("You must choose either resourceName=%v, labelSelector.matchLabels=%v or labelSelect.matchExpressions=%v", resourceName, labelSelector.MatchLabels, labelSelector.MatchExpressions)
|
log.Warningf("You must choose either resourceName=%v, labelSelector.matchLabels=%v or labelSelect.matchExpressions=%v", resourceName, labelSelector.MatchLabels, labelSelector.MatchExpressions)
|
||||||
return nil, fmt.Errorf("There is a problem with the definition of the ClusterDecisionResource generator")
|
return nil, fmt.Errorf("There is a problem with the definition of the ClusterDecisionResource generator")
|
||||||
}
|
}
|
||||||
@@ -118,14 +122,15 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
|
|||||||
log.WithField("listOptions.LabelSelector", listOptions.LabelSelector).Info("selection type")
|
log.WithField("listOptions.LabelSelector", listOptions.LabelSelector).Info("selection type")
|
||||||
} else {
|
} else {
|
||||||
listOptions.FieldSelector = fields.OneTermEqualSelector("metadata.name", resourceName).String()
|
listOptions.FieldSelector = fields.OneTermEqualSelector("metadata.name", resourceName).String()
|
||||||
// metav1.Convert_fields_Selector_To_string(fields.).Sprintf("metadata.name=%s", resourceName)
|
//metav1.Convert_fields_Selector_To_string(fields.).Sprintf("metadata.name=%s", resourceName)
|
||||||
log.WithField("listOptions.FieldSelector", listOptions.FieldSelector).Info("selection type")
|
log.WithField("listOptions.FieldSelector", listOptions.FieldSelector).Info("selection type")
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -144,6 +149,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
|
|||||||
if matchKey == "" {
|
if matchKey == "" {
|
||||||
log.WithField("matchKey", matchKey).Warning("matchKey not found in " + cm.Name)
|
log.WithField("matchKey", matchKey).Warning("matchKey not found in " + cm.Name)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res := []map[string]interface{}{}
|
res := []map[string]interface{}{}
|
||||||
@@ -161,6 +167,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
|
|||||||
log.WithField("duckResourceStatus", duckResource.Object["status"]).Debug("found resource")
|
log.WithField("duckResourceStatus", duckResource.Object["status"]).Debug("found resource")
|
||||||
|
|
||||||
clusterDecisions = append(clusterDecisions, duckResource.Object["status"].(map[string]interface{})[statusListKey].([]interface{})...)
|
clusterDecisions = append(clusterDecisions, duckResource.Object["status"].(map[string]interface{})[statusListKey].([]interface{})...)
|
||||||
|
|
||||||
}
|
}
|
||||||
log.Infof("Number of decisions found: %v", len(clusterDecisions))
|
log.Infof("Number of decisions found: %v", len(clusterDecisions))
|
||||||
|
|
||||||
@@ -169,6 +176,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
|
|||||||
|
|
||||||
if len(clusterDecisions) > 0 {
|
if len(clusterDecisions) > 0 {
|
||||||
for _, cluster := range clusterDecisions {
|
for _, cluster := range clusterDecisions {
|
||||||
|
|
||||||
// generated instance of cluster params
|
// generated instance of cluster params
|
||||||
params := map[string]interface{}{}
|
params := map[string]interface{}{}
|
||||||
|
|
||||||
@@ -186,6 +194,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
|
|||||||
|
|
||||||
for _, argoCluster := range argoClusters {
|
for _, argoCluster := range argoClusters {
|
||||||
if argoCluster.Name == strMatchValue {
|
if argoCluster.Name == strMatchValue {
|
||||||
|
|
||||||
log.WithField(matchKey, argoCluster.Name).Info("matched cluster in ArgoCD")
|
log.WithField(matchKey, argoCluster.Name).Info("matched cluster in ArgoCD")
|
||||||
params["name"] = argoCluster.Name
|
params["name"] = argoCluster.Name
|
||||||
params["server"] = argoCluster.Server
|
params["server"] = argoCluster.Server
|
||||||
@@ -193,6 +202,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
|
|||||||
found = true
|
found = true
|
||||||
break // Stop looking
|
break // Stop looking
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
@@ -218,7 +228,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ package generators
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
@@ -17,13 +15,13 @@ 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"
|
||||||
|
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const resourceApiVersion = "mallard.io/v1"
|
||||||
resourceApiVersion = "mallard.io/v1"
|
const resourceKind = "ducks"
|
||||||
resourceKind = "ducks"
|
const resourceName = "quak"
|
||||||
resourceName = "quak"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGenerateParamsForDuckType(t *testing.T) {
|
func TestGenerateParamsForDuckType(t *testing.T) {
|
||||||
clusters := []client.Object{
|
clusters := []client.Object{
|
||||||
@@ -282,7 +280,9 @@ func TestGenerateParamsForDuckType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
|
||||||
appClientset := kubefake.NewSimpleClientset(append(runtimeClusters, configMap)...)
|
appClientset := kubefake.NewSimpleClientset(append(runtimeClusters, configMap)...)
|
||||||
|
|
||||||
gvrToListKind := map[schema.GroupVersionResource]string{{
|
gvrToListKind := map[schema.GroupVersionResource]string{{
|
||||||
@@ -293,7 +293,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
|
|||||||
|
|
||||||
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, testCase.resource)
|
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, testCase.resource)
|
||||||
|
|
||||||
duckTypeGenerator := NewDuckTypeGenerator(context.Background(), fakeDynClient, appClientset, "namespace")
|
var duckTypeGenerator = NewDuckTypeGenerator(context.Background(), fakeDynClient, appClientset, "namespace")
|
||||||
|
|
||||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
@@ -309,12 +309,12 @@ func TestGenerateParamsForDuckType(t *testing.T) {
|
|||||||
LabelSelector: testCase.labelSelector,
|
LabelSelector: testCase.labelSelector,
|
||||||
Values: testCase.values,
|
Values: testCase.values,
|
||||||
},
|
},
|
||||||
}, &applicationSetInfo, nil)
|
}, &applicationSetInfo)
|
||||||
|
|
||||||
if testCase.expectedError != nil {
|
if testCase.expectedError != nil {
|
||||||
require.EqualError(t, err, testCase.expectedError.Error())
|
assert.EqualError(t, err, testCase.expectedError.Error())
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.ElementsMatch(t, testCase.expected, got)
|
assert.ElementsMatch(t, testCase.expected, got)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -578,7 +578,9 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
|
||||||
appClientset := kubefake.NewSimpleClientset(append(runtimeClusters, configMap)...)
|
appClientset := kubefake.NewSimpleClientset(append(runtimeClusters, configMap)...)
|
||||||
|
|
||||||
gvrToListKind := map[schema.GroupVersionResource]string{{
|
gvrToListKind := map[schema.GroupVersionResource]string{{
|
||||||
@@ -589,7 +591,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
|
|||||||
|
|
||||||
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, testCase.resource)
|
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, testCase.resource)
|
||||||
|
|
||||||
duckTypeGenerator := NewDuckTypeGenerator(context.Background(), fakeDynClient, appClientset, "namespace")
|
var duckTypeGenerator = NewDuckTypeGenerator(context.Background(), fakeDynClient, appClientset, "namespace")
|
||||||
|
|
||||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
@@ -607,12 +609,12 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
|
|||||||
LabelSelector: testCase.labelSelector,
|
LabelSelector: testCase.labelSelector,
|
||||||
Values: testCase.values,
|
Values: testCase.values,
|
||||||
},
|
},
|
||||||
}, &applicationSetInfo, nil)
|
}, &applicationSetInfo)
|
||||||
|
|
||||||
if testCase.expectedError != nil {
|
if testCase.expectedError != nil {
|
||||||
require.EqualError(t, err, testCase.expectedError.Error())
|
assert.EqualError(t, err, testCase.expectedError.Error())
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.ElementsMatch(t, testCase.expected, got)
|
assert.ElementsMatch(t, testCase.expected, got)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ package generators
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"encoding/json"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/jeremywohl/flatten"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
|
|
||||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
|
||||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||||
@@ -26,12 +25,9 @@ type TransformResult struct {
|
|||||||
Template argoprojiov1alpha1.ApplicationSetTemplate
|
Template argoprojiov1alpha1.ApplicationSetTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform a spec generator to list of paramSets and a template
|
//Transform a spec generator to list of paramSets and a template
|
||||||
func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, allGenerators map[string]Generator, baseTemplate argoprojiov1alpha1.ApplicationSetTemplate, appSet *argoprojiov1alpha1.ApplicationSet, genParams map[string]interface{}, client client.Client) ([]TransformResult, error) {
|
func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, allGenerators map[string]Generator, baseTemplate argoprojiov1alpha1.ApplicationSetTemplate, appSet *argoprojiov1alpha1.ApplicationSet, genParams map[string]interface{}) ([]TransformResult, error) {
|
||||||
// This is a custom version of the `LabelSelectorAsSelector` that is in k8s.io/apimachinery. This has been copied
|
selector, err := metav1.LabelSelectorAsSelector(requestedGenerator.Selector)
|
||||||
// verbatim from that package, with the difference that we do not have any restrictions on label values. This is done
|
|
||||||
// so that, among other things, we can match on cluster urls.
|
|
||||||
selector, err := utils.LabelSelectorAsSelector(requestedGenerator.Selector)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error parsing label selector: %w", err)
|
return nil, fmt.Errorf("error parsing label selector: %w", err)
|
||||||
}
|
}
|
||||||
@@ -54,7 +50,7 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
|
|||||||
}
|
}
|
||||||
var params []map[string]interface{}
|
var params []map[string]interface{}
|
||||||
if len(genParams) != 0 {
|
if len(genParams) != 0 {
|
||||||
tempInterpolatedGenerator, err := InterpolateGenerator(&requestedGenerator, genParams, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
|
tempInterpolatedGenerator, err := InterpolateGenerator(&requestedGenerator, genParams, appSet.Spec.GoTemplate)
|
||||||
interpolatedGenerator = &tempInterpolatedGenerator
|
interpolatedGenerator = &tempInterpolatedGenerator
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).WithField("genParams", genParams).
|
log.WithError(err).WithField("genParams", genParams).
|
||||||
@@ -65,7 +61,7 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
params, err = g.GenerateParams(interpolatedGenerator, appSet, client)
|
params, err = g.GenerateParams(interpolatedGenerator, appSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).WithField("generator", g).
|
log.WithError(err).WithField("generator", g).
|
||||||
Error("error generating params")
|
Error("error generating params")
|
||||||
@@ -76,17 +72,8 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
|
|||||||
}
|
}
|
||||||
var filterParams []map[string]interface{}
|
var filterParams []map[string]interface{}
|
||||||
for _, param := range params {
|
for _, param := range params {
|
||||||
flatParam, err := flattenParameters(param)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).WithField("generator", g).
|
|
||||||
Error("error flattening params")
|
|
||||||
if firstError == nil {
|
|
||||||
firstError = err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if requestedGenerator.Selector != nil && !selector.Matches(labels.Set(flatParam)) {
|
if requestedGenerator.Selector != nil && !selector.Matches(labels.Set(keepOnlyStringValues(param))) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
filterParams = append(filterParams, param)
|
filterParams = append(filterParams, param)
|
||||||
@@ -101,6 +88,18 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
|
|||||||
return res, firstError
|
return res, firstError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func keepOnlyStringValues(in map[string]interface{}) map[string]string {
|
||||||
|
var out map[string]string = map[string]string{}
|
||||||
|
|
||||||
|
for key, value := range in {
|
||||||
|
if _, ok := value.(string); ok {
|
||||||
|
out[key] = value.(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func GetRelevantGenerators(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, generators map[string]Generator) []Generator {
|
func GetRelevantGenerators(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, generators map[string]Generator) []Generator {
|
||||||
var res []Generator
|
var res []Generator
|
||||||
|
|
||||||
@@ -123,20 +122,6 @@ func GetRelevantGenerators(requestedGenerator *argoprojiov1alpha1.ApplicationSet
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func flattenParameters(in map[string]interface{}) (map[string]string, error) {
|
|
||||||
flat, err := flatten.Flatten(in, "", flatten.DotStyle)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error flatenning parameters: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
out := make(map[string]string, len(flat))
|
|
||||||
for k, v := range flat {
|
|
||||||
out[k] = fmt.Sprintf("%v", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeGeneratorTemplate(g Generator, requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetTemplate argoprojiov1alpha1.ApplicationSetTemplate) (argoprojiov1alpha1.ApplicationSetTemplate, error) {
|
func mergeGeneratorTemplate(g Generator, requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetTemplate argoprojiov1alpha1.ApplicationSetTemplate) (argoprojiov1alpha1.ApplicationSetTemplate, error) {
|
||||||
// Make a copy of the value from `GetTemplate()` before merge, rather than copying directly into
|
// Make a copy of the value from `GetTemplate()` before merge, rather than copying directly into
|
||||||
// the provided parameter (which will touch the original resource object returned by client-go)
|
// the provided parameter (which will touch the original resource object returned by client-go)
|
||||||
@@ -147,28 +132,27 @@ func mergeGeneratorTemplate(g Generator, requestedGenerator *argoprojiov1alpha1.
|
|||||||
return *dest, err
|
return *dest, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// InterpolateGenerator allows interpolating the matrix's 2nd child generator with values from the 1st child generator
|
// Currently for Matrix Generator. Allows interpolating the matrix's 2nd child generator with values from the 1st child generator
|
||||||
// "params" parameter is an array, where each index corresponds to a generator. Each index contains a map w/ that generator's parameters.
|
// "params" parameter is an array, where each index corresponds to a generator. Each index contains a map w/ that generator's parameters.
|
||||||
func InterpolateGenerator(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (argoprojiov1alpha1.ApplicationSetGenerator, error) {
|
func InterpolateGenerator(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, params map[string]interface{}, useGoTemplate bool) (argoprojiov1alpha1.ApplicationSetGenerator, error) {
|
||||||
render := utils.Render{}
|
interpolatedGenerator := requestedGenerator.DeepCopy()
|
||||||
interpolatedGenerator, err := render.RenderGeneratorParams(requestedGenerator, params, useGoTemplate, goTemplateOptions)
|
tmplBytes, err := json.Marshal(interpolatedGenerator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).WithField("interpolatedGenerator", interpolatedGenerator).Error("error interpolating generator with other generator's parameter")
|
log.WithError(err).WithField("requestedGenerator", interpolatedGenerator).Error("error marshalling requested generator for interpolation")
|
||||||
return argoprojiov1alpha1.ApplicationSetGenerator{}, err
|
return *interpolatedGenerator, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render := utils.Render{}
|
||||||
|
replacedTmplStr, err := render.Replace(string(tmplBytes), params, useGoTemplate)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).WithField("interpolatedGeneratorString", replacedTmplStr).Error("error interpolating generator with other generator's parameter")
|
||||||
|
return *interpolatedGenerator, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(replacedTmplStr), interpolatedGenerator)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).WithField("requestedGenerator", interpolatedGenerator).Error("error unmarshalling requested generator for interpolation")
|
||||||
|
return *interpolatedGenerator, err
|
||||||
|
}
|
||||||
return *interpolatedGenerator, nil
|
return *interpolatedGenerator, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fixes https://github.com/argoproj/argo-cd/issues/11982 while ensuring backwards compatibility.
|
|
||||||
// This is only a short-term solution and should be removed in a future major version.
|
|
||||||
func dropDisabledNestedSelectors(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator) bool {
|
|
||||||
var foundSelector bool
|
|
||||||
for i := range generators {
|
|
||||||
if generators[i].Selector != nil {
|
|
||||||
foundSelector = true
|
|
||||||
generators[i].Selector = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return foundSelector
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,12 +6,9 @@ import (
|
|||||||
|
|
||||||
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"
|
|
||||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
"github.com/argoproj/argo-cd/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"
|
||||||
|
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
@@ -20,6 +17,8 @@ import (
|
|||||||
kubefake "k8s.io/client-go/kubernetes/fake"
|
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||||
crtclient "sigs.k8s.io/controller-runtime/pkg/client"
|
crtclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||||
|
|
||||||
|
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMatchValues(t *testing.T) {
|
func TestMatchValues(t *testing.T) {
|
||||||
@@ -65,195 +64,36 @@ func TestMatchValues(t *testing.T) {
|
|||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
listGenerator := NewListGenerator()
|
var listGenerator = NewListGenerator()
|
||||||
data := map[string]Generator{
|
var data = map[string]Generator{
|
||||||
"List": listGenerator,
|
"List": listGenerator,
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationSetInfo := argov1alpha1.ApplicationSet{
|
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "set",
|
Name: "set",
|
||||||
},
|
},
|
||||||
Spec: argov1alpha1.ApplicationSetSpec{
|
Spec: argoprojiov1alpha1.ApplicationSetSpec{},
|
||||||
GoTemplate: false,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
results, err := Transform(argov1alpha1.ApplicationSetGenerator{
|
results, err := Transform(argoprojiov1alpha1.ApplicationSetGenerator{
|
||||||
Selector: testCase.selector,
|
Selector: testCase.selector,
|
||||||
List: &argov1alpha1.ListGenerator{
|
List: &argoprojiov1alpha1.ListGenerator{
|
||||||
Elements: testCase.elements,
|
Elements: testCase.elements,
|
||||||
Template: emptyTemplate(),
|
Template: emptyTemplate(),
|
||||||
},
|
}},
|
||||||
},
|
|
||||||
data,
|
data,
|
||||||
emptyTemplate(),
|
emptyTemplate(),
|
||||||
&applicationSetInfo, nil, nil)
|
&applicationSetInfo, nil)
|
||||||
|
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.ElementsMatch(t, testCase.expected, results[0].Params)
|
assert.ElementsMatch(t, testCase.expected, results[0].Params)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMatchValuesGoTemplate(t *testing.T) {
|
func emptyTemplate() argoprojiov1alpha1.ApplicationSetTemplate {
|
||||||
testCases := []struct {
|
return argoprojiov1alpha1.ApplicationSetTemplate{
|
||||||
name string
|
|
||||||
elements []apiextensionsv1.JSON
|
|
||||||
selector *metav1.LabelSelector
|
|
||||||
expected []map[string]interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "no filter",
|
|
||||||
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
|
|
||||||
selector: &metav1.LabelSelector{},
|
|
||||||
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "nil",
|
|
||||||
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
|
|
||||||
selector: nil,
|
|
||||||
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "values.foo should be foo but is ignore element",
|
|
||||||
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
|
|
||||||
selector: &metav1.LabelSelector{
|
|
||||||
MatchLabels: map[string]string{
|
|
||||||
"values.foo": "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: []map[string]interface{}{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "values.foo should be bar",
|
|
||||||
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
|
|
||||||
selector: &metav1.LabelSelector{
|
|
||||||
MatchLabels: map[string]string{
|
|
||||||
"values.foo": "bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values": map[string]interface{}{"foo": "bar"}}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "values.0 should be bar",
|
|
||||||
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":["bar"]}`)}},
|
|
||||||
selector: &metav1.LabelSelector{
|
|
||||||
MatchLabels: map[string]string{
|
|
||||||
"values.0": "bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values": []interface{}{"bar"}}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
|
||||||
listGenerator := NewListGenerator()
|
|
||||||
data := map[string]Generator{
|
|
||||||
"List": listGenerator,
|
|
||||||
}
|
|
||||||
|
|
||||||
applicationSetInfo := argov1alpha1.ApplicationSet{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "set",
|
|
||||||
},
|
|
||||||
Spec: argov1alpha1.ApplicationSetSpec{
|
|
||||||
GoTemplate: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
results, err := Transform(argov1alpha1.ApplicationSetGenerator{
|
|
||||||
Selector: testCase.selector,
|
|
||||||
List: &argov1alpha1.ListGenerator{
|
|
||||||
Elements: testCase.elements,
|
|
||||||
Template: emptyTemplate(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data,
|
|
||||||
emptyTemplate(),
|
|
||||||
&applicationSetInfo, nil, nil)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.ElementsMatch(t, testCase.expected, results[0].Params)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTransForm(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
selector *metav1.LabelSelector
|
|
||||||
expected []map[string]interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "server filter",
|
|
||||||
selector: &metav1.LabelSelector{
|
|
||||||
MatchLabels: map[string]string{"server": "https://production-01.example.com"},
|
|
||||||
},
|
|
||||||
expected: []map[string]interface{}{{
|
|
||||||
"metadata.annotations.foo.argoproj.io": "production",
|
|
||||||
"metadata.labels.argocd.argoproj.io/secret-type": "cluster",
|
|
||||||
"metadata.labels.environment": "production",
|
|
||||||
"metadata.labels.org": "bar",
|
|
||||||
"name": "production_01/west",
|
|
||||||
"nameNormalized": "production-01-west",
|
|
||||||
"server": "https://production-01.example.com",
|
|
||||||
"project": "",
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "server filter with long url",
|
|
||||||
selector: &metav1.LabelSelector{
|
|
||||||
MatchLabels: map[string]string{"server": "https://some-really-long-url-that-will-exceed-63-characters.com"},
|
|
||||||
},
|
|
||||||
expected: []map[string]interface{}{{
|
|
||||||
"metadata.annotations.foo.argoproj.io": "production",
|
|
||||||
"metadata.labels.argocd.argoproj.io/secret-type": "cluster",
|
|
||||||
"metadata.labels.environment": "production",
|
|
||||||
"metadata.labels.org": "bar",
|
|
||||||
"name": "some-really-long-server-url",
|
|
||||||
"nameNormalized": "some-really-long-server-url",
|
|
||||||
"server": "https://some-really-long-url-that-will-exceed-63-characters.com",
|
|
||||||
"project": "",
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
|
||||||
testGenerators := map[string]Generator{
|
|
||||||
"Clusters": getMockClusterGenerator(),
|
|
||||||
}
|
|
||||||
|
|
||||||
applicationSetInfo := argov1alpha1.ApplicationSet{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "set",
|
|
||||||
},
|
|
||||||
Spec: argov1alpha1.ApplicationSetSpec{},
|
|
||||||
}
|
|
||||||
|
|
||||||
results, err := Transform(
|
|
||||||
argov1alpha1.ApplicationSetGenerator{
|
|
||||||
Selector: testCase.selector,
|
|
||||||
Clusters: &argov1alpha1.ClusterGenerator{
|
|
||||||
Selector: metav1.LabelSelector{},
|
|
||||||
Template: argov1alpha1.ApplicationSetTemplate{},
|
|
||||||
Values: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
testGenerators,
|
|
||||||
emptyTemplate(),
|
|
||||||
&applicationSetInfo, nil, nil)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.ElementsMatch(t, testCase.expected, results[0].Params)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func emptyTemplate() argov1alpha1.ApplicationSetTemplate {
|
|
||||||
return argov1alpha1.ApplicationSetTemplate{
|
|
||||||
Spec: argov1alpha1.ApplicationSpec{
|
Spec: argov1alpha1.ApplicationSpec{
|
||||||
Project: "project",
|
Project: "project",
|
||||||
},
|
},
|
||||||
@@ -310,35 +150,8 @@ func getMockClusterGenerator() Generator {
|
|||||||
},
|
},
|
||||||
Type: corev1.SecretType("Opaque"),
|
Type: corev1.SecretType("Opaque"),
|
||||||
},
|
},
|
||||||
&corev1.Secret{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
Kind: "Secret",
|
|
||||||
APIVersion: "v1",
|
|
||||||
},
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "some-really-long-server-url",
|
|
||||||
Namespace: "namespace",
|
|
||||||
Labels: map[string]string{
|
|
||||||
"argocd.argoproj.io/secret-type": "cluster",
|
|
||||||
"environment": "production",
|
|
||||||
"org": "bar",
|
|
||||||
},
|
|
||||||
Annotations: map[string]string{
|
|
||||||
"foo.argoproj.io": "production",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{
|
|
||||||
"config": []byte("{}"),
|
|
||||||
"name": []byte("some-really-long-server-url"),
|
|
||||||
"server": []byte("https://some-really-long-url-that-will-exceed-63-characters.com"),
|
|
||||||
},
|
|
||||||
Type: corev1.SecretType("Opaque"),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
runtimeClusters := []runtime.Object{}
|
runtimeClusters := []runtime.Object{}
|
||||||
for _, clientCluster := range clusters {
|
|
||||||
runtimeClusters = append(runtimeClusters, clientCluster)
|
|
||||||
}
|
|
||||||
appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
|
appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
|
||||||
|
|
||||||
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
|
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
|
||||||
@@ -346,13 +159,14 @@ func getMockClusterGenerator() Generator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getMockGitGenerator() Generator {
|
func getMockGitGenerator() Generator {
|
||||||
argoCDServiceMock := mocks.Repos{}
|
argoCDServiceMock := argoCDServiceMock{mock: &mock.Mock{}}
|
||||||
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return([]string{"app1", "app2", "app_3", "p1/app4"}, nil)
|
argoCDServiceMock.mock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return([]string{"app1", "app2", "app_3", "p1/app4"}, nil)
|
||||||
gitGenerator := NewGitGenerator(&argoCDServiceMock, "namespace")
|
var gitGenerator = NewGitGenerator(argoCDServiceMock)
|
||||||
return gitGenerator
|
return gitGenerator
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetRelevantGenerators(t *testing.T) {
|
func TestGetRelevantGenerators(t *testing.T) {
|
||||||
|
|
||||||
testGenerators := map[string]Generator{
|
testGenerators := map[string]Generator{
|
||||||
"Clusters": getMockClusterGenerator(),
|
"Clusters": getMockClusterGenerator(),
|
||||||
"Git": getMockGitGenerator(),
|
"Git": getMockGitGenerator(),
|
||||||
@@ -362,20 +176,19 @@ func TestGetRelevantGenerators(t *testing.T) {
|
|||||||
testGenerators["Merge"] = NewMergeGenerator(testGenerators)
|
testGenerators["Merge"] = NewMergeGenerator(testGenerators)
|
||||||
testGenerators["List"] = NewListGenerator()
|
testGenerators["List"] = NewListGenerator()
|
||||||
|
|
||||||
requestedGenerator := &argov1alpha1.ApplicationSetGenerator{
|
requestedGenerator := &argoprojiov1alpha1.ApplicationSetGenerator{
|
||||||
List: &argov1alpha1.ListGenerator{
|
List: &argoprojiov1alpha1.ListGenerator{
|
||||||
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
|
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
|
||||||
},
|
}}
|
||||||
}
|
|
||||||
|
|
||||||
relevantGenerators := GetRelevantGenerators(requestedGenerator, testGenerators)
|
relevantGenerators := GetRelevantGenerators(requestedGenerator, testGenerators)
|
||||||
assert.Len(t, relevantGenerators, 1)
|
assert.Len(t, relevantGenerators, 1)
|
||||||
assert.IsType(t, &ListGenerator{}, relevantGenerators[0])
|
assert.IsType(t, &ListGenerator{}, relevantGenerators[0])
|
||||||
|
|
||||||
requestedGenerator = &argov1alpha1.ApplicationSetGenerator{
|
requestedGenerator = &argoprojiov1alpha1.ApplicationSetGenerator{
|
||||||
Clusters: &argov1alpha1.ClusterGenerator{
|
Clusters: &argoprojiov1alpha1.ClusterGenerator{
|
||||||
Selector: metav1.LabelSelector{},
|
Selector: metav1.LabelSelector{},
|
||||||
Template: argov1alpha1.ApplicationSetTemplate{},
|
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
|
||||||
Values: nil,
|
Values: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -384,14 +197,14 @@ func TestGetRelevantGenerators(t *testing.T) {
|
|||||||
assert.Len(t, relevantGenerators, 1)
|
assert.Len(t, relevantGenerators, 1)
|
||||||
assert.IsType(t, &ClusterGenerator{}, relevantGenerators[0])
|
assert.IsType(t, &ClusterGenerator{}, relevantGenerators[0])
|
||||||
|
|
||||||
requestedGenerator = &argov1alpha1.ApplicationSetGenerator{
|
requestedGenerator = &argoprojiov1alpha1.ApplicationSetGenerator{
|
||||||
Git: &argov1alpha1.GitGenerator{
|
Git: &argoprojiov1alpha1.GitGenerator{
|
||||||
RepoURL: "",
|
RepoURL: "",
|
||||||
Directories: nil,
|
Directories: nil,
|
||||||
Files: nil,
|
Files: nil,
|
||||||
Revision: "",
|
Revision: "",
|
||||||
RequeueAfterSeconds: nil,
|
RequeueAfterSeconds: nil,
|
||||||
Template: argov1alpha1.ApplicationSetTemplate{},
|
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,16 +214,15 @@ func TestGetRelevantGenerators(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInterpolateGenerator(t *testing.T) {
|
func TestInterpolateGenerator(t *testing.T) {
|
||||||
requestedGenerator := &argov1alpha1.ApplicationSetGenerator{
|
requestedGenerator := &argoprojiov1alpha1.ApplicationSetGenerator{
|
||||||
Clusters: &argov1alpha1.ClusterGenerator{
|
Clusters: &argoprojiov1alpha1.ClusterGenerator{
|
||||||
Selector: metav1.LabelSelector{
|
Selector: metav1.LabelSelector{
|
||||||
MatchLabels: map[string]string{
|
MatchLabels: map[string]string{
|
||||||
"argocd.argoproj.io/secret-type": "cluster",
|
"argocd.argoproj.io/secret-type": "cluster",
|
||||||
"path-basename": "{{path.basename}}",
|
"path-basename": "{{path.basename}}",
|
||||||
"path-zero": "{{path[0]}}",
|
"path-zero": "{{path[0]}}",
|
||||||
"path-full": "{{path}}",
|
"path-full": "{{path}}",
|
||||||
},
|
}},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
gitGeneratorParams := map[string]interface{}{
|
gitGeneratorParams := map[string]interface{}{
|
||||||
@@ -420,7 +232,7 @@ func TestInterpolateGenerator(t *testing.T) {
|
|||||||
"path[1]": "p2",
|
"path[1]": "p2",
|
||||||
"path.basenameNormalized": "app3",
|
"path.basenameNormalized": "app3",
|
||||||
}
|
}
|
||||||
interpolatedGenerator, err := InterpolateGenerator(requestedGenerator, gitGeneratorParams, false, nil)
|
interpolatedGenerator, err := InterpolateGenerator(requestedGenerator, gitGeneratorParams, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
|
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
|
||||||
return
|
return
|
||||||
@@ -429,23 +241,23 @@ func TestInterpolateGenerator(t *testing.T) {
|
|||||||
assert.Equal(t, "p1", interpolatedGenerator.Clusters.Selector.MatchLabels["path-zero"])
|
assert.Equal(t, "p1", interpolatedGenerator.Clusters.Selector.MatchLabels["path-zero"])
|
||||||
assert.Equal(t, "p1/p2/app3", interpolatedGenerator.Clusters.Selector.MatchLabels["path-full"])
|
assert.Equal(t, "p1/p2/app3", interpolatedGenerator.Clusters.Selector.MatchLabels["path-full"])
|
||||||
|
|
||||||
fileNamePath := argov1alpha1.GitFileGeneratorItem{
|
fileNamePath := argoprojiov1alpha1.GitFileGeneratorItem{
|
||||||
Path: "{{name}}",
|
Path: "{{name}}",
|
||||||
}
|
}
|
||||||
fileServerPath := argov1alpha1.GitFileGeneratorItem{
|
fileServerPath := argoprojiov1alpha1.GitFileGeneratorItem{
|
||||||
Path: "{{server}}",
|
Path: "{{server}}",
|
||||||
}
|
}
|
||||||
|
|
||||||
requestedGenerator = &argov1alpha1.ApplicationSetGenerator{
|
requestedGenerator = &argoprojiov1alpha1.ApplicationSetGenerator{
|
||||||
Git: &argov1alpha1.GitGenerator{
|
Git: &argoprojiov1alpha1.GitGenerator{
|
||||||
Files: append([]argov1alpha1.GitFileGeneratorItem{}, fileNamePath, fileServerPath),
|
Files: append([]argoprojiov1alpha1.GitFileGeneratorItem{}, fileNamePath, fileServerPath),
|
||||||
Template: argov1alpha1.ApplicationSetTemplate{},
|
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
clusterGeneratorParams := map[string]interface{}{
|
clusterGeneratorParams := map[string]interface{}{
|
||||||
"name": "production_01/west", "server": "https://production-01.example.com",
|
"name": "production_01/west", "server": "https://production-01.example.com",
|
||||||
}
|
}
|
||||||
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, false, nil)
|
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
|
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
|
||||||
return
|
return
|
||||||
@@ -453,115 +265,3 @@ func TestInterpolateGenerator(t *testing.T) {
|
|||||||
assert.Equal(t, "production_01/west", interpolatedGenerator.Git.Files[0].Path)
|
assert.Equal(t, "production_01/west", interpolatedGenerator.Git.Files[0].Path)
|
||||||
assert.Equal(t, "https://production-01.example.com", interpolatedGenerator.Git.Files[1].Path)
|
assert.Equal(t, "https://production-01.example.com", interpolatedGenerator.Git.Files[1].Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInterpolateGenerator_go(t *testing.T) {
|
|
||||||
requestedGenerator := &argov1alpha1.ApplicationSetGenerator{
|
|
||||||
Clusters: &argov1alpha1.ClusterGenerator{
|
|
||||||
Selector: metav1.LabelSelector{
|
|
||||||
MatchLabels: map[string]string{
|
|
||||||
"argocd.argoproj.io/secret-type": "cluster",
|
|
||||||
"path-basename": "{{base .path.path}}",
|
|
||||||
"path-zero": "{{index .path.segments 0}}",
|
|
||||||
"path-full": "{{.path.path}}",
|
|
||||||
"kubernetes.io/environment": `{{default "foo" .my_label}}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
gitGeneratorParams := map[string]interface{}{
|
|
||||||
"path": map[string]interface{}{
|
|
||||||
"path": "p1/p2/app3",
|
|
||||||
"segments": []string{"p1", "p2", "app3"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
interpolatedGenerator, err := InterpolateGenerator(requestedGenerator, gitGeneratorParams, true, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
assert.Equal(t, "app3", interpolatedGenerator.Clusters.Selector.MatchLabels["path-basename"])
|
|
||||||
assert.Equal(t, "p1", interpolatedGenerator.Clusters.Selector.MatchLabels["path-zero"])
|
|
||||||
assert.Equal(t, "p1/p2/app3", interpolatedGenerator.Clusters.Selector.MatchLabels["path-full"])
|
|
||||||
|
|
||||||
fileNamePath := argov1alpha1.GitFileGeneratorItem{
|
|
||||||
Path: "{{.name}}",
|
|
||||||
}
|
|
||||||
fileServerPath := argov1alpha1.GitFileGeneratorItem{
|
|
||||||
Path: "{{.server}}",
|
|
||||||
}
|
|
||||||
|
|
||||||
requestedGenerator = &argov1alpha1.ApplicationSetGenerator{
|
|
||||||
Git: &argov1alpha1.GitGenerator{
|
|
||||||
Files: append([]argov1alpha1.GitFileGeneratorItem{}, fileNamePath, fileServerPath),
|
|
||||||
Template: argov1alpha1.ApplicationSetTemplate{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
clusterGeneratorParams := map[string]interface{}{
|
|
||||||
"name": "production_01/west", "server": "https://production-01.example.com",
|
|
||||||
}
|
|
||||||
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, true, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
assert.Equal(t, "production_01/west", interpolatedGenerator.Git.Files[0].Path)
|
|
||||||
assert.Equal(t, "https://production-01.example.com", interpolatedGenerator.Git.Files[1].Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInterpolateGeneratorError(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
requestedGenerator *argov1alpha1.ApplicationSetGenerator
|
|
||||||
params map[string]interface{}
|
|
||||||
useGoTemplate bool
|
|
||||||
goTemplateOptions []string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want argov1alpha1.ApplicationSetGenerator
|
|
||||||
expectedErrStr string
|
|
||||||
}{
|
|
||||||
{name: "Empty Gen", args: args{
|
|
||||||
requestedGenerator: nil,
|
|
||||||
params: nil,
|
|
||||||
useGoTemplate: false,
|
|
||||||
goTemplateOptions: nil,
|
|
||||||
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: "generator is empty"},
|
|
||||||
{name: "No Params", args: args{
|
|
||||||
requestedGenerator: &argov1alpha1.ApplicationSetGenerator{},
|
|
||||||
params: map[string]interface{}{},
|
|
||||||
useGoTemplate: false,
|
|
||||||
goTemplateOptions: nil,
|
|
||||||
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: ""},
|
|
||||||
{name: "Error templating", args: args{
|
|
||||||
requestedGenerator: &argov1alpha1.ApplicationSetGenerator{Git: &argov1alpha1.GitGenerator{
|
|
||||||
RepoURL: "foo",
|
|
||||||
Files: []argov1alpha1.GitFileGeneratorItem{{Path: "bar/"}},
|
|
||||||
Revision: "main",
|
|
||||||
Values: map[string]string{
|
|
||||||
"git_test": "{{ toPrettyJson . }}",
|
|
||||||
"selection": "{{ default .override .test }}",
|
|
||||||
"resolved": "{{ index .rmap (default .override .test) }}",
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
params: map[string]interface{}{
|
|
||||||
"name": "in-cluster",
|
|
||||||
"override": "foo",
|
|
||||||
},
|
|
||||||
useGoTemplate: true,
|
|
||||||
goTemplateOptions: []string{},
|
|
||||||
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: "failed to replace parameters in generator: failed to execute go template {{ index .rmap (default .override .test) }}: template: :1:3: executing \"\" at <index .rmap (default .override .test)>: error calling index: index of untyped nil"},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := InterpolateGenerator(tt.args.requestedGenerator, tt.args.params, tt.args.useGoTemplate, tt.args.goTemplateOptions)
|
|
||||||
if tt.expectedErrStr != "" {
|
|
||||||
require.EqualError(t, err, tt.expectedErrStr)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
assert.Equalf(t, tt.want, got, "InterpolateGenerator(%v, %v, %v, %v)", tt.args.requestedGenerator, tt.args.params, tt.args.useGoTemplate, tt.args.goTemplateOptions)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,29 +11,23 @@ import (
|
|||||||
|
|
||||||
"github.com/jeremywohl/flatten"
|
"github.com/jeremywohl/flatten"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
"github.com/argoproj/argo-cd/v2/applicationset/services"
|
"github.com/argoproj/argo-cd/v2/applicationset/services"
|
||||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
"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"
|
||||||
"github.com/argoproj/argo-cd/v2/util/gpg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Generator = (*GitGenerator)(nil)
|
var _ Generator = (*GitGenerator)(nil)
|
||||||
|
|
||||||
type GitGenerator struct {
|
type GitGenerator struct {
|
||||||
repos services.Repos
|
repos services.Repos
|
||||||
namespace string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGitGenerator(repos services.Repos, namespace string) Generator {
|
func NewGitGenerator(repos services.Repos) Generator {
|
||||||
g := &GitGenerator{
|
g := &GitGenerator{
|
||||||
repos: repos,
|
repos: repos,
|
||||||
namespace: namespace,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,16 +36,18 @@ func (g *GitGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applicati
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
|
func (g *GitGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
|
||||||
|
|
||||||
// Return a requeue default of 3 minutes, if no default is specified.
|
// Return a requeue default of 3 minutes, if no default is specified.
|
||||||
|
|
||||||
if appSetGenerator.Git.RequeueAfterSeconds != nil {
|
if appSetGenerator.Git.RequeueAfterSeconds != nil {
|
||||||
return time.Duration(*appSetGenerator.Git.RequeueAfterSeconds) * time.Second
|
return time.Duration(*appSetGenerator.Git.RequeueAfterSeconds) * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
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) ([]map[string]interface{}, error) {
|
||||||
|
|
||||||
if appSetGenerator == nil {
|
if appSetGenerator == nil {
|
||||||
return nil, EmptyAppSetGeneratorError
|
return nil, EmptyAppSetGeneratorError
|
||||||
}
|
}
|
||||||
@@ -60,73 +56,51 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
|
|||||||
return nil, EmptyAppSetGeneratorError
|
return nil, EmptyAppSetGeneratorError
|
||||||
}
|
}
|
||||||
|
|
||||||
noRevisionCache := appSet.RefreshRequired()
|
|
||||||
|
|
||||||
verifyCommit := false
|
|
||||||
|
|
||||||
// When the project field is templated, the contents of the git repo are required to run the git generator and get the templated value,
|
|
||||||
// but git generator cannot be called without verifying the commit signature.
|
|
||||||
// In this case, we skip the signature verification.
|
|
||||||
if !strings.Contains(appSet.Spec.Template.Spec.Project, "{{") {
|
|
||||||
project := appSet.Spec.Template.Spec.Project
|
|
||||||
appProject := &argoprojiov1alpha1.AppProject{}
|
|
||||||
namespace := g.namespace
|
|
||||||
if namespace == "" {
|
|
||||||
namespace = appSet.Namespace
|
|
||||||
}
|
|
||||||
if err := client.Get(context.TODO(), types.NamespacedName{Name: project, Namespace: namespace}, appProject); err != nil {
|
|
||||||
return nil, fmt.Errorf("error getting project %s: %w", project, err)
|
|
||||||
}
|
|
||||||
// we need to verify the signature on the Git revision if GPG is enabled
|
|
||||||
verifyCommit = len(appProject.Spec.SignatureKeys) > 0 && gpg.IsGPGEnabled()
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var res []map[string]interface{}
|
var res []map[string]interface{}
|
||||||
if len(appSetGenerator.Git.Directories) != 0 {
|
if appSetGenerator.Git.Directories != nil {
|
||||||
res, err = g.generateParamsForGitDirectories(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
|
res, err = g.generateParamsForGitDirectories(appSetGenerator, appSet.Spec.GoTemplate)
|
||||||
} else if len(appSetGenerator.Git.Files) != 0 {
|
} else if appSetGenerator.Git.Files != nil {
|
||||||
res, err = g.generateParamsForGitFiles(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
|
res, err = g.generateParamsForGitFiles(appSetGenerator, appSet.Spec.GoTemplate)
|
||||||
} else {
|
} else {
|
||||||
return nil, EmptyAppSetGeneratorError
|
return nil, EmptyAppSetGeneratorError
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error generating params from git: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
|
func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool) ([]map[string]interface{}, error) {
|
||||||
|
|
||||||
// Directories, not files
|
// Directories, not files
|
||||||
allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, noRevisionCache, verifyCommit)
|
allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting directories from repo: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"allPaths": allPaths,
|
"allPaths": allPaths,
|
||||||
"total": len(allPaths),
|
"total": len(allPaths),
|
||||||
"repoURL": appSetGenerator.Git.RepoURL,
|
"repoURL": appSetGenerator.Git.RepoURL,
|
||||||
"revision": appSetGenerator.Git.Revision,
|
"revision": appSetGenerator.Git.Revision,
|
||||||
"pathParamPrefix": appSetGenerator.Git.PathParamPrefix,
|
"pathParamPrefix": appSetGenerator.Git.PathParamPrefix,
|
||||||
}).Info("applications result from the repo service")
|
}).Info("applications result from the repo service")
|
||||||
|
|
||||||
requestedApps := g.filterApps(appSetGenerator.Git.Directories, allPaths)
|
requestedApps := g.filterApps(appSetGenerator.Git.Directories, allPaths)
|
||||||
|
|
||||||
res, err := g.generateParamsFromApps(requestedApps, appSetGenerator, useGoTemplate, goTemplateOptions)
|
res := g.generateParamsFromApps(requestedApps, appSetGenerator, useGoTemplate)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating params from apps: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
|
func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool) ([]map[string]interface{}, error) {
|
||||||
|
|
||||||
// Get all files that match the requested path string, removing duplicates
|
// Get all files that match the requested path string, removing duplicates
|
||||||
allFiles := make(map[string][]byte)
|
allFiles := make(map[string][]byte)
|
||||||
for _, requestedPath := range appSetGenerator.Git.Files {
|
for _, requestedPath := range appSetGenerator.Git.Files {
|
||||||
files, err := g.repos.GetFiles(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, requestedPath.Path, noRevisionCache, verifyCommit)
|
files, err := g.repos.GetFiles(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, requestedPath.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -146,18 +120,21 @@ func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1al
|
|||||||
// Generate params from each path, and return
|
// Generate params from each path, and return
|
||||||
res := []map[string]interface{}{}
|
res := []map[string]interface{}{}
|
||||||
for _, path := range allPaths {
|
for _, path := range allPaths {
|
||||||
|
|
||||||
// A JSON / YAML file path can contain multiple sets of parameters (ie it is an array)
|
// A JSON / YAML file path can contain multiple sets of parameters (ie it is an array)
|
||||||
paramsArray, err := g.generateParamsFromGitFile(path, allFiles[path], appSetGenerator.Git.Values, useGoTemplate, goTemplateOptions, appSetGenerator.Git.PathParamPrefix)
|
paramsArray, err := g.generateParamsFromGitFile(path, allFiles[path], useGoTemplate, appSetGenerator.Git.PathParamPrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to process file '%s': %w", path, err)
|
return nil, fmt.Errorf("unable to process file '%s': %v", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
res = append(res, paramsArray...)
|
for index := range paramsArray {
|
||||||
|
res = append(res, paramsArray[index])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []byte, values map[string]string, useGoTemplate bool, goTemplateOptions []string, pathParamPrefix string) ([]map[string]interface{}, error) {
|
func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []byte, useGoTemplate bool, pathParamPrefix string) ([]map[string]interface{}, error) {
|
||||||
objectsFound := []map[string]interface{}{}
|
objectsFound := []map[string]interface{}{}
|
||||||
|
|
||||||
// First, we attempt to parse as an array
|
// First, we attempt to parse as an array
|
||||||
@@ -167,17 +144,15 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
|
|||||||
singleObj := make(map[string]interface{})
|
singleObj := make(map[string]interface{})
|
||||||
err = yaml.Unmarshal(fileContent, &singleObj)
|
err = yaml.Unmarshal(fileContent, &singleObj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to parse file: %w", err)
|
return nil, fmt.Errorf("unable to parse file: %v", err)
|
||||||
}
|
}
|
||||||
objectsFound = append(objectsFound, singleObj)
|
objectsFound = append(objectsFound, singleObj)
|
||||||
} else if len(objectsFound) == 0 {
|
|
||||||
// If file is valid but empty, add a default empty item
|
|
||||||
objectsFound = append(objectsFound, map[string]interface{}{})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res := []map[string]interface{}{}
|
res := []map[string]interface{}{}
|
||||||
|
|
||||||
for _, objectFound := range objectsFound {
|
for _, objectFound := range objectsFound {
|
||||||
|
|
||||||
params := map[string]interface{}{}
|
params := map[string]interface{}{}
|
||||||
|
|
||||||
if useGoTemplate {
|
if useGoTemplate {
|
||||||
@@ -201,14 +176,14 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
|
|||||||
} else {
|
} else {
|
||||||
flat, err := flatten.Flatten(objectFound, "", flatten.DotStyle)
|
flat, err := flatten.Flatten(objectFound, "", flatten.DotStyle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error flattening object: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
for k, v := range flat {
|
for k, v := range flat {
|
||||||
params[k] = fmt.Sprintf("%v", v)
|
params[k] = fmt.Sprintf("%v", v)
|
||||||
}
|
}
|
||||||
pathParamName := "path"
|
pathParamName := "path"
|
||||||
if pathParamPrefix != "" {
|
if pathParamPrefix != "" {
|
||||||
pathParamName = pathParamPrefix + "." + pathParamName
|
pathParamName = pathParamPrefix+"."+pathParamName
|
||||||
}
|
}
|
||||||
params[pathParamName] = path.Dir(filePath)
|
params[pathParamName] = path.Dir(filePath)
|
||||||
params[pathParamName+".basename"] = path.Base(params[pathParamName].(string))
|
params[pathParamName+".basename"] = path.Base(params[pathParamName].(string))
|
||||||
@@ -222,24 +197,19 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := appendTemplatedValues(values, params, useGoTemplate, goTemplateOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to append templated values: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
res = append(res, params)
|
res = append(res, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitGenerator) filterApps(directories []argoprojiov1alpha1.GitDirectoryGeneratorItem, allPaths []string) []string {
|
func (g *GitGenerator) filterApps(Directories []argoprojiov1alpha1.GitDirectoryGeneratorItem, allPaths []string) []string {
|
||||||
res := []string{}
|
res := []string{}
|
||||||
for _, appPath := range allPaths {
|
for _, appPath := range allPaths {
|
||||||
appInclude := false
|
appInclude := false
|
||||||
appExclude := false
|
appExclude := false
|
||||||
// Iterating over each appPath and check whether directories object has requestedPath that matches the appPath
|
// Iterating over each appPath and check whether directories object has requestedPath that matches the appPath
|
||||||
for _, requestedPath := range directories {
|
for _, requestedPath := range Directories {
|
||||||
match, err := path.Match(requestedPath.Path, appPath)
|
match, err := path.Match(requestedPath.Path, appPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).WithField("requestedPath", requestedPath).
|
log.WithError(err).WithField("requestedPath", requestedPath).
|
||||||
@@ -261,9 +231,10 @@ func (g *GitGenerator) filterApps(directories []argoprojiov1alpha1.GitDirectoryG
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
|
func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool) []map[string]interface{} {
|
||||||
res := make([]map[string]interface{}, len(requestedApps))
|
res := make([]map[string]interface{}, len(requestedApps))
|
||||||
for i, a := range requestedApps {
|
for i, a := range requestedApps {
|
||||||
|
|
||||||
params := make(map[string]interface{}, 5)
|
params := make(map[string]interface{}, 5)
|
||||||
|
|
||||||
if useGoTemplate {
|
if useGoTemplate {
|
||||||
@@ -280,7 +251,7 @@ func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGene
|
|||||||
} else {
|
} else {
|
||||||
pathParamName := "path"
|
pathParamName := "path"
|
||||||
if appSetGenerator.Git.PathParamPrefix != "" {
|
if appSetGenerator.Git.PathParamPrefix != "" {
|
||||||
pathParamName = appSetGenerator.Git.PathParamPrefix + "." + pathParamName
|
pathParamName = appSetGenerator.Git.PathParamPrefix+"."+pathParamName
|
||||||
}
|
}
|
||||||
params[pathParamName] = a
|
params[pathParamName] = a
|
||||||
params[pathParamName+".basename"] = path.Base(a)
|
params[pathParamName+".basename"] = path.Base(a)
|
||||||
@@ -292,13 +263,8 @@ func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGene
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := appendTemplatedValues(appSetGenerator.Git.Values, params, useGoTemplate, goTemplateOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to append templated values: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
res[i] = params
|
res[i] = params
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,191 +1,162 @@
|
|||||||
package generators
|
package generators
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
||||||
|
|
||||||
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
|
|
||||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
|
||||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// type clientSet struct {
|
||||||
|
// RepoServerServiceClient apiclient.RepoServerServiceClient
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (c *clientSet) NewRepoServerClient() (io.Closer, apiclient.RepoServerServiceClient, error) {
|
||||||
|
// return io.NewCloser(func() error { return nil }), c.RepoServerServiceClient, nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
type argoCDServiceMock struct {
|
||||||
|
mock *mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a argoCDServiceMock) GetApps(ctx context.Context, repoURL string, revision string) ([]string, error) {
|
||||||
|
args := a.mock.Called(ctx, repoURL, revision)
|
||||||
|
|
||||||
|
return args.Get(0).([]string), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a argoCDServiceMock) GetFiles(ctx context.Context, repoURL string, revision string, pattern string) (map[string][]byte, error) {
|
||||||
|
args := a.mock.Called(ctx, repoURL, revision, pattern)
|
||||||
|
|
||||||
|
return args.Get(0).(map[string][]byte), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a argoCDServiceMock) GetFileContent(ctx context.Context, repoURL string, revision string, path string) ([]byte, error) {
|
||||||
|
args := a.mock.Called(ctx, repoURL, revision, path)
|
||||||
|
|
||||||
|
return args.Get(0).([]byte), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a argoCDServiceMock) GetDirectories(ctx context.Context, repoURL string, revision string) ([]string, error) {
|
||||||
|
args := a.mock.Called(ctx, repoURL, revision)
|
||||||
|
return args.Get(0).([]string), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
func Test_generateParamsFromGitFile(t *testing.T) {
|
func Test_generateParamsFromGitFile(t *testing.T) {
|
||||||
defaultContent := []byte(`
|
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
|
||||||
foo:
|
foo:
|
||||||
bar: baz
|
bar: baz
|
||||||
`)
|
`), false, "")
|
||||||
type args struct {
|
if err != nil {
|
||||||
filePath string
|
t.Fatal(err)
|
||||||
fileContent []byte
|
|
||||||
values map[string]string
|
|
||||||
useGoTemplate bool
|
|
||||||
goTemplateOptions []string
|
|
||||||
pathParamPrefix string
|
|
||||||
}
|
}
|
||||||
tests := []struct {
|
assert.Equal(t, []map[string]interface{}{
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want []map[string]interface{}
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
{
|
||||||
name: "empty file returns path parameters",
|
"foo.bar": "baz",
|
||||||
args: args{
|
"path": "path/dir",
|
||||||
filePath: "path/dir/file_name.yaml",
|
"path.basename": "dir",
|
||||||
fileContent: []byte(""),
|
"path.filename": "file_name.yaml",
|
||||||
values: map[string]string{},
|
"path.basenameNormalized": "dir",
|
||||||
useGoTemplate: false,
|
"path.filenameNormalized": "file-name.yaml",
|
||||||
},
|
"path[0]": "path",
|
||||||
want: []map[string]interface{}{
|
"path[1]": "dir",
|
||||||
{
|
|
||||||
"path": "path/dir",
|
|
||||||
"path.basename": "dir",
|
|
||||||
"path.filename": "file_name.yaml",
|
|
||||||
"path.basenameNormalized": "dir",
|
|
||||||
"path.filenameNormalized": "file-name.yaml",
|
|
||||||
"path[0]": "path",
|
|
||||||
"path[1]": "dir",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid json/yaml file returns error",
|
|
||||||
args: args{
|
|
||||||
filePath: "path/dir/file_name.yaml",
|
|
||||||
fileContent: []byte("this is not json or yaml"),
|
|
||||||
values: map[string]string{},
|
|
||||||
useGoTemplate: false,
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "file parameters are added to params",
|
|
||||||
args: args{
|
|
||||||
filePath: "path/dir/file_name.yaml",
|
|
||||||
fileContent: defaultContent,
|
|
||||||
values: map[string]string{},
|
|
||||||
useGoTemplate: false,
|
|
||||||
},
|
|
||||||
want: []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"foo.bar": "baz",
|
|
||||||
"path": "path/dir",
|
|
||||||
"path.basename": "dir",
|
|
||||||
"path.filename": "file_name.yaml",
|
|
||||||
"path.basenameNormalized": "dir",
|
|
||||||
"path.filenameNormalized": "file-name.yaml",
|
|
||||||
"path[0]": "path",
|
|
||||||
"path[1]": "dir",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "path parameter are prefixed",
|
|
||||||
args: args{
|
|
||||||
filePath: "path/dir/file_name.yaml",
|
|
||||||
fileContent: defaultContent,
|
|
||||||
values: map[string]string{},
|
|
||||||
useGoTemplate: false,
|
|
||||||
pathParamPrefix: "myRepo",
|
|
||||||
},
|
|
||||||
want: []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"foo.bar": "baz",
|
|
||||||
"myRepo.path": "path/dir",
|
|
||||||
"myRepo.path.basename": "dir",
|
|
||||||
"myRepo.path.filename": "file_name.yaml",
|
|
||||||
"myRepo.path.basenameNormalized": "dir",
|
|
||||||
"myRepo.path.filenameNormalized": "file-name.yaml",
|
|
||||||
"myRepo.path[0]": "path",
|
|
||||||
"myRepo.path[1]": "dir",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "file parameters are added to params with go template",
|
|
||||||
args: args{
|
|
||||||
filePath: "path/dir/file_name.yaml",
|
|
||||||
fileContent: defaultContent,
|
|
||||||
values: map[string]string{},
|
|
||||||
useGoTemplate: true,
|
|
||||||
},
|
|
||||||
want: []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"foo": map[string]interface{}{
|
|
||||||
"bar": "baz",
|
|
||||||
},
|
|
||||||
"path": map[string]interface{}{
|
|
||||||
"path": "path/dir",
|
|
||||||
"basename": "dir",
|
|
||||||
"filename": "file_name.yaml",
|
|
||||||
"basenameNormalized": "dir",
|
|
||||||
"filenameNormalized": "file-name.yaml",
|
|
||||||
"segments": []string{
|
|
||||||
"path",
|
|
||||||
"dir",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "path parameter are prefixed with go template",
|
|
||||||
args: args{
|
|
||||||
filePath: "path/dir/file_name.yaml",
|
|
||||||
fileContent: defaultContent,
|
|
||||||
values: map[string]string{},
|
|
||||||
useGoTemplate: true,
|
|
||||||
pathParamPrefix: "myRepo",
|
|
||||||
},
|
|
||||||
want: []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"foo": map[string]interface{}{
|
|
||||||
"bar": "baz",
|
|
||||||
},
|
|
||||||
"myRepo": map[string]interface{}{
|
|
||||||
"path": map[string]interface{}{
|
|
||||||
"path": "path/dir",
|
|
||||||
"basename": "dir",
|
|
||||||
"filename": "file_name.yaml",
|
|
||||||
"basenameNormalized": "dir",
|
|
||||||
"filenameNormalized": "file-name.yaml",
|
|
||||||
"segments": []string{
|
|
||||||
"path",
|
|
||||||
"dir",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
}, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_generatePrefixedParamsFromGitFile(t *testing.T) {
|
||||||
|
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
|
||||||
|
foo:
|
||||||
|
bar: baz
|
||||||
|
`), false, "myRepo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
assert.Equal(t, []map[string]interface{}{
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
{
|
||||||
params, err := (*GitGenerator)(nil).generateParamsFromGitFile(tt.args.filePath, tt.args.fileContent, tt.args.values, tt.args.useGoTemplate, tt.args.goTemplateOptions, tt.args.pathParamPrefix)
|
"foo.bar": "baz",
|
||||||
if (err != nil) != tt.wantErr {
|
"myRepo.path": "path/dir",
|
||||||
t.Errorf("GitGenerator.generateParamsFromGitFile() error = %v, wantErr %v", err, tt.wantErr)
|
"myRepo.path.basename": "dir",
|
||||||
return
|
"myRepo.path.filename": "file_name.yaml",
|
||||||
}
|
"myRepo.path.basenameNormalized": "dir",
|
||||||
assert.Equal(t, tt.want, params)
|
"myRepo.path.filenameNormalized": "file-name.yaml",
|
||||||
})
|
"myRepo.path[0]": "path",
|
||||||
|
"myRepo.path[1]": "dir",
|
||||||
|
},
|
||||||
|
}, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_generateParamsFromGitFileGoTemplate(t *testing.T) {
|
||||||
|
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
|
||||||
|
foo:
|
||||||
|
bar: baz
|
||||||
|
`), true, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
assert.Equal(t, []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": "baz",
|
||||||
|
},
|
||||||
|
"path": map[string]interface{}{
|
||||||
|
"path": "path/dir",
|
||||||
|
"basename": "dir",
|
||||||
|
"filename": "file_name.yaml",
|
||||||
|
"basenameNormalized": "dir",
|
||||||
|
"filenameNormalized": "file-name.yaml",
|
||||||
|
"segments": []string{
|
||||||
|
"path",
|
||||||
|
"dir",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_generatePrefixedParamsFromGitFileGoTemplate(t *testing.T) {
|
||||||
|
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
|
||||||
|
foo:
|
||||||
|
bar: baz
|
||||||
|
`), true, "myRepo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": "baz",
|
||||||
|
},
|
||||||
|
"myRepo": map[string]interface{}{
|
||||||
|
"path": map[string]interface{}{
|
||||||
|
"path": "path/dir",
|
||||||
|
"basename": "dir",
|
||||||
|
"filename": "file_name.yaml",
|
||||||
|
"basenameNormalized": "dir",
|
||||||
|
"filenameNormalized": "file-name.yaml",
|
||||||
|
"segments": []string{
|
||||||
|
"path",
|
||||||
|
"dir",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitGenerateParamsFromDirectories(t *testing.T) {
|
func TestGitGenerateParamsFromDirectories(t *testing.T) {
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
directories []argoprojiov1alpha1.GitDirectoryGeneratorItem
|
directories []argoprojiov1alpha1.GitDirectoryGeneratorItem
|
||||||
pathParamPrefix string
|
pathParamPrefix string
|
||||||
repoApps []string
|
repoApps []string
|
||||||
repoError error
|
repoError error
|
||||||
values map[string]string
|
|
||||||
expected []map[string]interface{}
|
expected []map[string]interface{}
|
||||||
expectedError error
|
expectedError error
|
||||||
}{
|
}{
|
||||||
@@ -276,25 +247,6 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Value variable interpolation",
|
|
||||||
directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}, {Path: "*/*"}},
|
|
||||||
repoApps: []string{
|
|
||||||
"app1",
|
|
||||||
"p1/app2",
|
|
||||||
},
|
|
||||||
repoError: nil,
|
|
||||||
values: map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
"aaa": "{{ path[0] }}",
|
|
||||||
"no-op": "{{ this-does-not-exist }}",
|
|
||||||
},
|
|
||||||
expected: []map[string]interface{}{
|
|
||||||
{"values.foo": "bar", "values.no-op": "{{ this-does-not-exist }}", "values.aaa": "app1", "path": "app1", "path.basename": "app1", "path[0]": "app1", "path.basenameNormalized": "app1"},
|
|
||||||
{"values.foo": "bar", "values.no-op": "{{ this-does-not-exist }}", "values.aaa": "p1", "path": "p1/app2", "path.basename": "app2", "path[0]": "p1", "path[1]": "app2", "path.basenameNormalized": "app2"},
|
|
||||||
},
|
|
||||||
expectedError: nil,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "handles empty response from repo server",
|
name: "handles empty response from repo server",
|
||||||
directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
|
directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
|
||||||
@@ -309,7 +261,7 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
|
|||||||
repoApps: []string{},
|
repoApps: []string{},
|
||||||
repoError: fmt.Errorf("error"),
|
repoError: fmt.Errorf("error"),
|
||||||
expected: []map[string]interface{}{},
|
expected: []map[string]interface{}{},
|
||||||
expectedError: fmt.Errorf("error generating params from git: error getting directories from repo: error"),
|
expectedError: fmt.Errorf("error"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,11 +271,11 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
|
|||||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
t.Run(testCaseCopy.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
argoCDServiceMock := mocks.Repos{}
|
argoCDServiceMock := argoCDServiceMock{mock: &mock.Mock{}}
|
||||||
|
|
||||||
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
|
argoCDServiceMock.mock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
|
||||||
|
|
||||||
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
|
var gitGenerator = NewGitGenerator(argoCDServiceMock)
|
||||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "set",
|
Name: "set",
|
||||||
@@ -335,34 +287,27 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
|
|||||||
Revision: "Revision",
|
Revision: "Revision",
|
||||||
Directories: testCaseCopy.directories,
|
Directories: testCaseCopy.directories,
|
||||||
PathParamPrefix: testCaseCopy.pathParamPrefix,
|
PathParamPrefix: testCaseCopy.pathParamPrefix,
|
||||||
Values: testCaseCopy.values,
|
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
scheme := runtime.NewScheme()
|
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
|
||||||
err := v1alpha1.AddToScheme(scheme)
|
|
||||||
require.NoError(t, err)
|
|
||||||
appProject := argoprojiov1alpha1.AppProject{}
|
|
||||||
|
|
||||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
|
|
||||||
|
|
||||||
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, client)
|
|
||||||
|
|
||||||
if testCaseCopy.expectedError != nil {
|
if testCaseCopy.expectedError != nil {
|
||||||
require.EqualError(t, err, testCaseCopy.expectedError.Error())
|
assert.EqualError(t, err, testCaseCopy.expectedError.Error())
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, testCaseCopy.expected, got)
|
assert.Equal(t, testCaseCopy.expected, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
argoCDServiceMock.AssertExpectations(t)
|
argoCDServiceMock.mock.AssertExpectations(t)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
|
func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
directories []argoprojiov1alpha1.GitDirectoryGeneratorItem
|
directories []argoprojiov1alpha1.GitDirectoryGeneratorItem
|
||||||
@@ -562,6 +507,7 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
repoError: nil,
|
repoError: nil,
|
||||||
expected: []map[string]interface{}{
|
expected: []map[string]interface{}{
|
||||||
|
|
||||||
{
|
{
|
||||||
"path": map[string]interface{}{
|
"path": map[string]interface{}{
|
||||||
"path": "app1",
|
"path": "app1",
|
||||||
@@ -610,7 +556,7 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
|
|||||||
repoApps: []string{},
|
repoApps: []string{},
|
||||||
repoError: fmt.Errorf("error"),
|
repoError: fmt.Errorf("error"),
|
||||||
expected: []map[string]interface{}{},
|
expected: []map[string]interface{}{},
|
||||||
expectedError: fmt.Errorf("error generating params from git: error getting directories from repo: error"),
|
expectedError: fmt.Errorf("error"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,11 +566,11 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
|
|||||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
t.Run(testCaseCopy.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
argoCDServiceMock := mocks.Repos{}
|
argoCDServiceMock := argoCDServiceMock{mock: &mock.Mock{}}
|
||||||
|
|
||||||
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
|
argoCDServiceMock.mock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
|
||||||
|
|
||||||
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
|
var gitGenerator = NewGitGenerator(argoCDServiceMock)
|
||||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "set",
|
Name: "set",
|
||||||
@@ -642,28 +588,23 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
scheme := runtime.NewScheme()
|
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
|
||||||
err := v1alpha1.AddToScheme(scheme)
|
|
||||||
require.NoError(t, err)
|
|
||||||
appProject := argoprojiov1alpha1.AppProject{}
|
|
||||||
|
|
||||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
|
|
||||||
|
|
||||||
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, client)
|
|
||||||
|
|
||||||
if testCaseCopy.expectedError != nil {
|
if testCaseCopy.expectedError != nil {
|
||||||
require.EqualError(t, err, testCaseCopy.expectedError.Error())
|
assert.EqualError(t, err, testCaseCopy.expectedError.Error())
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, testCaseCopy.expected, got)
|
assert.Equal(t, testCaseCopy.expected, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
argoCDServiceMock.AssertExpectations(t)
|
argoCDServiceMock.mock.AssertExpectations(t)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitGenerateParamsFromFiles(t *testing.T) {
|
func TestGitGenerateParamsFromFiles(t *testing.T) {
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
// files is the list of paths/globs to match
|
// files is the list of paths/globs to match
|
||||||
@@ -672,7 +613,6 @@ func TestGitGenerateParamsFromFiles(t *testing.T) {
|
|||||||
repoFileContents map[string][]byte
|
repoFileContents map[string][]byte
|
||||||
// if repoPathsError is non-nil, the call to GetPaths(...) will return this error value
|
// if repoPathsError is non-nil, the call to GetPaths(...) will return this error value
|
||||||
repoPathsError error
|
repoPathsError error
|
||||||
values map[string]string
|
|
||||||
expected []map[string]interface{}
|
expected []map[string]interface{}
|
||||||
expectedError error
|
expectedError error
|
||||||
}{
|
}{
|
||||||
@@ -736,81 +676,13 @@ func TestGitGenerateParamsFromFiles(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Value variable interpolation",
|
|
||||||
files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}},
|
|
||||||
repoFileContents: map[string][]byte{
|
|
||||||
"cluster-config/production/config.json": []byte(`{
|
|
||||||
"cluster": {
|
|
||||||
"owner": "john.doe@example.com",
|
|
||||||
"name": "production",
|
|
||||||
"address": "https://kubernetes.default.svc"
|
|
||||||
},
|
|
||||||
"key1": "val1",
|
|
||||||
"key2": {
|
|
||||||
"key2_1": "val2_1",
|
|
||||||
"key2_2": {
|
|
||||||
"key2_2_1": "val2_2_1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"key3": 123
|
|
||||||
}`),
|
|
||||||
"cluster-config/staging/config.json": []byte(`{
|
|
||||||
"cluster": {
|
|
||||||
"owner": "foo.bar@example.com",
|
|
||||||
"name": "staging",
|
|
||||||
"address": "https://kubernetes.default.svc"
|
|
||||||
}
|
|
||||||
}`),
|
|
||||||
},
|
|
||||||
repoPathsError: nil,
|
|
||||||
values: map[string]string{
|
|
||||||
"aaa": "{{ cluster.owner }}",
|
|
||||||
"no-op": "{{ this-does-not-exist }}",
|
|
||||||
},
|
|
||||||
expected: []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"cluster.owner": "john.doe@example.com",
|
|
||||||
"cluster.name": "production",
|
|
||||||
"cluster.address": "https://kubernetes.default.svc",
|
|
||||||
"key1": "val1",
|
|
||||||
"key2.key2_1": "val2_1",
|
|
||||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
|
||||||
"key3": "123",
|
|
||||||
"path": "cluster-config/production",
|
|
||||||
"path.basename": "production",
|
|
||||||
"path[0]": "cluster-config",
|
|
||||||
"path[1]": "production",
|
|
||||||
"path.basenameNormalized": "production",
|
|
||||||
"path.filename": "config.json",
|
|
||||||
"path.filenameNormalized": "config.json",
|
|
||||||
"values.aaa": "john.doe@example.com",
|
|
||||||
"values.no-op": "{{ this-does-not-exist }}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cluster.owner": "foo.bar@example.com",
|
|
||||||
"cluster.name": "staging",
|
|
||||||
"cluster.address": "https://kubernetes.default.svc",
|
|
||||||
"path": "cluster-config/staging",
|
|
||||||
"path.basename": "staging",
|
|
||||||
"path[0]": "cluster-config",
|
|
||||||
"path[1]": "staging",
|
|
||||||
"path.basenameNormalized": "staging",
|
|
||||||
"path.filename": "config.json",
|
|
||||||
"path.filenameNormalized": "config.json",
|
|
||||||
"values.aaa": "foo.bar@example.com",
|
|
||||||
"values.no-op": "{{ this-does-not-exist }}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedError: nil,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "handles error during getting repo paths",
|
name: "handles error during getting repo paths",
|
||||||
files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}},
|
files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}},
|
||||||
repoFileContents: map[string][]byte{},
|
repoFileContents: map[string][]byte{},
|
||||||
repoPathsError: fmt.Errorf("paths error"),
|
repoPathsError: fmt.Errorf("paths error"),
|
||||||
expected: []map[string]interface{}{},
|
expected: []map[string]interface{}{},
|
||||||
expectedError: fmt.Errorf("error generating params from git: paths error"),
|
expectedError: fmt.Errorf("paths error"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test invalid JSON file returns error",
|
name: "test invalid JSON file returns error",
|
||||||
@@ -820,7 +692,7 @@ func TestGitGenerateParamsFromFiles(t *testing.T) {
|
|||||||
},
|
},
|
||||||
repoPathsError: nil,
|
repoPathsError: nil,
|
||||||
expected: []map[string]interface{}{},
|
expected: []map[string]interface{}{},
|
||||||
expectedError: fmt.Errorf("error generating params from git: unable to process file 'cluster-config/production/config.json': unable to parse file: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}"),
|
expectedError: fmt.Errorf("unable to process file 'cluster-config/production/config.json': unable to parse file: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test JSON array",
|
name: "test JSON array",
|
||||||
@@ -985,11 +857,11 @@ cluster:
|
|||||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
t.Run(testCaseCopy.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
argoCDServiceMock := mocks.Repos{}
|
argoCDServiceMock := argoCDServiceMock{mock: &mock.Mock{}}
|
||||||
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
argoCDServiceMock.mock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
||||||
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
|
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
|
||||||
|
|
||||||
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
|
var gitGenerator = NewGitGenerator(argoCDServiceMock)
|
||||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "set",
|
Name: "set",
|
||||||
@@ -1000,35 +872,28 @@ cluster:
|
|||||||
RepoURL: "RepoURL",
|
RepoURL: "RepoURL",
|
||||||
Revision: "Revision",
|
Revision: "Revision",
|
||||||
Files: testCaseCopy.files,
|
Files: testCaseCopy.files,
|
||||||
Values: testCaseCopy.values,
|
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
scheme := runtime.NewScheme()
|
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
|
||||||
err := v1alpha1.AddToScheme(scheme)
|
|
||||||
require.NoError(t, err)
|
|
||||||
appProject := argoprojiov1alpha1.AppProject{}
|
|
||||||
|
|
||||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
|
|
||||||
|
|
||||||
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, client)
|
|
||||||
fmt.Println(got, err)
|
fmt.Println(got, err)
|
||||||
|
|
||||||
if testCaseCopy.expectedError != nil {
|
if testCaseCopy.expectedError != nil {
|
||||||
require.EqualError(t, err, testCaseCopy.expectedError.Error())
|
assert.EqualError(t, err, testCaseCopy.expectedError.Error())
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.ElementsMatch(t, testCaseCopy.expected, got)
|
assert.ElementsMatch(t, testCaseCopy.expected, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
argoCDServiceMock.AssertExpectations(t)
|
argoCDServiceMock.mock.AssertExpectations(t)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitGenerateParamsFromFilesGoTemplate(t *testing.T) {
|
func TestGitGenerateParamsFromFilesGoTemplate(t *testing.T) {
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
// files is the list of paths/globs to match
|
// files is the list of paths/globs to match
|
||||||
@@ -1122,7 +987,7 @@ func TestGitGenerateParamsFromFilesGoTemplate(t *testing.T) {
|
|||||||
repoFileContents: map[string][]byte{},
|
repoFileContents: map[string][]byte{},
|
||||||
repoPathsError: fmt.Errorf("paths error"),
|
repoPathsError: fmt.Errorf("paths error"),
|
||||||
expected: []map[string]interface{}{},
|
expected: []map[string]interface{}{},
|
||||||
expectedError: fmt.Errorf("error generating params from git: paths error"),
|
expectedError: fmt.Errorf("paths error"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test invalid JSON file returns error",
|
name: "test invalid JSON file returns error",
|
||||||
@@ -1132,7 +997,7 @@ func TestGitGenerateParamsFromFilesGoTemplate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
repoPathsError: nil,
|
repoPathsError: nil,
|
||||||
expected: []map[string]interface{}{},
|
expected: []map[string]interface{}{},
|
||||||
expectedError: fmt.Errorf("error generating params from git: unable to process file 'cluster-config/production/config.json': unable to parse file: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}"),
|
expectedError: fmt.Errorf("unable to process file 'cluster-config/production/config.json': unable to parse file: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test JSON array",
|
name: "test JSON array",
|
||||||
@@ -1341,11 +1206,11 @@ cluster:
|
|||||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
t.Run(testCaseCopy.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
argoCDServiceMock := mocks.Repos{}
|
argoCDServiceMock := argoCDServiceMock{mock: &mock.Mock{}}
|
||||||
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
argoCDServiceMock.mock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
||||||
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
|
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
|
||||||
|
|
||||||
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
|
var gitGenerator = NewGitGenerator(argoCDServiceMock)
|
||||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "set",
|
Name: "set",
|
||||||
@@ -1362,135 +1227,17 @@ cluster:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
scheme := runtime.NewScheme()
|
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
|
||||||
err := v1alpha1.AddToScheme(scheme)
|
|
||||||
require.NoError(t, err)
|
|
||||||
appProject := argoprojiov1alpha1.AppProject{}
|
|
||||||
|
|
||||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
|
|
||||||
|
|
||||||
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, client)
|
|
||||||
fmt.Println(got, err)
|
fmt.Println(got, err)
|
||||||
|
|
||||||
if testCaseCopy.expectedError != nil {
|
if testCaseCopy.expectedError != nil {
|
||||||
require.EqualError(t, err, testCaseCopy.expectedError.Error())
|
assert.EqualError(t, err, testCaseCopy.expectedError.Error())
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.ElementsMatch(t, testCaseCopy.expected, got)
|
assert.ElementsMatch(t, testCaseCopy.expected, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
argoCDServiceMock.AssertExpectations(t)
|
argoCDServiceMock.mock.AssertExpectations(t)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitGenerator_GenerateParams(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
directories []argoprojiov1alpha1.GitDirectoryGeneratorItem
|
|
||||||
pathParamPrefix string
|
|
||||||
repoApps []string
|
|
||||||
repoPathsError error
|
|
||||||
repoFileContents map[string][]byte
|
|
||||||
values map[string]string
|
|
||||||
expected []map[string]interface{}
|
|
||||||
expectedError error
|
|
||||||
appset argoprojiov1alpha1.ApplicationSet
|
|
||||||
callGetDirectories bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Signature Verification - ignores templated project field",
|
|
||||||
repoApps: []string{
|
|
||||||
"app1",
|
|
||||||
},
|
|
||||||
repoPathsError: nil,
|
|
||||||
appset: argoprojiov1alpha1.ApplicationSet{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "set",
|
|
||||||
Namespace: "namespace",
|
|
||||||
},
|
|
||||||
Spec: argoprojiov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
|
|
||||||
Git: &argoprojiov1alpha1.GitGenerator{
|
|
||||||
RepoURL: "RepoURL",
|
|
||||||
Revision: "Revision",
|
|
||||||
Directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
|
|
||||||
PathParamPrefix: "",
|
|
||||||
Values: map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
Template: argoprojiov1alpha1.ApplicationSetTemplate{
|
|
||||||
Spec: argoprojiov1alpha1.ApplicationSpec{
|
|
||||||
Project: "{{.project}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
callGetDirectories: true,
|
|
||||||
expected: []map[string]interface{}{{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "path[0]": "app1", "values.foo": "bar"}},
|
|
||||||
expectedError: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Signature Verification - Checks for non-templated project field",
|
|
||||||
repoApps: []string{
|
|
||||||
"app1",
|
|
||||||
},
|
|
||||||
repoPathsError: nil,
|
|
||||||
appset: argoprojiov1alpha1.ApplicationSet{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "set",
|
|
||||||
Namespace: "namespace",
|
|
||||||
},
|
|
||||||
Spec: argoprojiov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
|
|
||||||
Git: &argoprojiov1alpha1.GitGenerator{
|
|
||||||
RepoURL: "RepoURL",
|
|
||||||
Revision: "Revision",
|
|
||||||
Directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
|
|
||||||
PathParamPrefix: "",
|
|
||||||
Values: map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
Template: argoprojiov1alpha1.ApplicationSetTemplate{
|
|
||||||
Spec: argoprojiov1alpha1.ApplicationSpec{
|
|
||||||
Project: "project",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
callGetDirectories: false,
|
|
||||||
expected: []map[string]interface{}{{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "path[0]": "app1", "values.foo": "bar"}},
|
|
||||||
expectedError: fmt.Errorf("error getting project project: appprojects.argoproj.io \"project\" not found"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, testCase := range cases {
|
|
||||||
argoCDServiceMock := mocks.Repos{}
|
|
||||||
|
|
||||||
if testCase.callGetDirectories {
|
|
||||||
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCase.repoApps, testCase.repoPathsError)
|
|
||||||
}
|
|
||||||
gitGenerator := NewGitGenerator(&argoCDServiceMock, "namespace")
|
|
||||||
|
|
||||||
scheme := runtime.NewScheme()
|
|
||||||
err := v1alpha1.AddToScheme(scheme)
|
|
||||||
require.NoError(t, err)
|
|
||||||
appProject := argoprojiov1alpha1.AppProject{}
|
|
||||||
|
|
||||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
|
|
||||||
|
|
||||||
got, err := gitGenerator.GenerateParams(&testCase.appset.Spec.Generators[0], &testCase.appset, client)
|
|
||||||
|
|
||||||
if testCase.expectedError != nil {
|
|
||||||
require.EqualError(t, err, testCase.expectedError.Error())
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, testCase.expected, got)
|
|
||||||
}
|
|
||||||
|
|
||||||
argoCDServiceMock.AssertExpectations(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,10 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Generator defines the interface implemented by all ApplicationSet generators.
|
// Generator defines the interface implemented by all ApplicationSet generators.
|
||||||
@@ -15,7 +12,7 @@ type Generator interface {
|
|||||||
// GenerateParams interprets the ApplicationSet and generates all relevant parameters for the application template.
|
// GenerateParams interprets the ApplicationSet and generates all relevant parameters for the application template.
|
||||||
// The expected / desired list of parameters is returned, it then will be render and reconciled
|
// The expected / desired list of parameters is returned, it then will be render and reconciled
|
||||||
// against the current state of the Applications in the cluster.
|
// against the current state of the Applications in the cluster.
|
||||||
GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error)
|
GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error)
|
||||||
|
|
||||||
// GetRequeueAfter is the generator can controller the next reconciled loop
|
// GetRequeueAfter is the generator can controller the next reconciled loop
|
||||||
// In case there is more then one generator the time will be the minimum of the times.
|
// In case there is more then one generator the time will be the minimum of the times.
|
||||||
@@ -26,16 +23,10 @@ type Generator interface {
|
|||||||
GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate
|
GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var EmptyAppSetGeneratorError = fmt.Errorf("ApplicationSet is empty")
|
||||||
EmptyAppSetGeneratorError = fmt.Errorf("ApplicationSet is empty")
|
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)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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()")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,15 +5,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
|
|
||||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Generator = (*ListGenerator)(nil)
|
var _ Generator = (*ListGenerator)(nil)
|
||||||
|
|
||||||
type ListGenerator struct{}
|
type ListGenerator struct {
|
||||||
|
}
|
||||||
|
|
||||||
func NewListGenerator() Generator {
|
func NewListGenerator() Generator {
|
||||||
g := &ListGenerator{}
|
g := &ListGenerator{}
|
||||||
@@ -28,7 +26,7 @@ func (g *ListGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applicat
|
|||||||
return &appSetGenerator.List.Template
|
return &appSetGenerator.List.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
|
func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
|
||||||
if appSetGenerator == nil {
|
if appSetGenerator == nil {
|
||||||
return nil, EmptyAppSetGeneratorError
|
return nil, EmptyAppSetGeneratorError
|
||||||
}
|
}
|
||||||
@@ -44,7 +42,7 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
|
|||||||
var element map[string]interface{}
|
var element map[string]interface{}
|
||||||
err := json.Unmarshal(tmpItem.Raw, &element)
|
err := json.Unmarshal(tmpItem.Raw, &element)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error unmarshling list element %w", err)
|
return nil, fmt.Errorf("error unmarshling list element %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if appSet.Spec.GoTemplate {
|
if appSet.Spec.GoTemplate {
|
||||||
@@ -59,14 +57,14 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
|
|||||||
for k, v := range values {
|
for k, v := range values {
|
||||||
value, ok := v.(string)
|
value, ok := v.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("error parsing value as string %w", err)
|
return nil, fmt.Errorf("error parsing value as string %v", err)
|
||||||
}
|
}
|
||||||
params[fmt.Sprintf("values.%s", k)] = value
|
params[fmt.Sprintf("values.%s", k)] = value
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
v, ok := value.(string)
|
v, ok := value.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("error parsing value as string %w", err)
|
return nil, fmt.Errorf("error parsing value as string %v", err)
|
||||||
}
|
}
|
||||||
params[key] = v
|
params[key] = v
|
||||||
}
|
}
|
||||||
@@ -75,15 +73,5 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append elements from ElementsYaml to the response
|
|
||||||
if len(appSetGenerator.List.ElementsYaml) > 0 {
|
|
||||||
var yamlElements []map[string]interface{}
|
|
||||||
err := yaml.Unmarshal([]byte(appSetGenerator.List.ElementsYaml), &yamlElements)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error unmarshling decoded ElementsYaml %w", err)
|
|
||||||
}
|
|
||||||
res = append(res, yamlElements...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
@@ -26,7 +25,8 @@ func TestGenerateListParams(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
listGenerator := NewListGenerator()
|
|
||||||
|
var listGenerator = NewListGenerator()
|
||||||
|
|
||||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
@@ -38,11 +38,11 @@ func TestGenerateListParams(t *testing.T) {
|
|||||||
got, err := listGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
|
got, err := listGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
|
||||||
List: &argoprojiov1alpha1.ListGenerator{
|
List: &argoprojiov1alpha1.ListGenerator{
|
||||||
Elements: testCase.elements,
|
Elements: testCase.elements,
|
||||||
},
|
}}, &applicationSetInfo)
|
||||||
}, &applicationSetInfo, nil)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.ElementsMatch(t, testCase.expected, got)
|
assert.ElementsMatch(t, testCase.expected, got)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +61,8 @@ func TestGenerateListParamsGoTemplate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
listGenerator := NewListGenerator()
|
|
||||||
|
var listGenerator = NewListGenerator()
|
||||||
|
|
||||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
@@ -75,10 +76,9 @@ func TestGenerateListParamsGoTemplate(t *testing.T) {
|
|||||||
got, err := listGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
|
got, err := listGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
|
||||||
List: &argoprojiov1alpha1.ListGenerator{
|
List: &argoprojiov1alpha1.ListGenerator{
|
||||||
Elements: testCase.elements,
|
Elements: testCase.elements,
|
||||||
},
|
}}, &applicationSetInfo)
|
||||||
}, &applicationSetInfo, nil)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.ElementsMatch(t, testCase.expected, got)
|
assert.ElementsMatch(t, testCase.expected, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
|
|
||||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
"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"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Generator = (*MatrixGenerator)(nil)
|
var _ Generator = (*MatrixGenerator)(nil)
|
||||||
@@ -33,7 +30,8 @@ func NewMatrixGenerator(supportedGenerators map[string]Generator) Generator {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error) {
|
func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
|
||||||
|
|
||||||
if appSetGenerator.Matrix == nil {
|
if appSetGenerator.Matrix == nil {
|
||||||
return nil, EmptyAppSetGeneratorError
|
return nil, EmptyAppSetGeneratorError
|
||||||
}
|
}
|
||||||
@@ -48,23 +46,24 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
|
|||||||
|
|
||||||
res := []map[string]interface{}{}
|
res := []map[string]interface{}{}
|
||||||
|
|
||||||
g0, err := m.getParams(appSetGenerator.Matrix.Generators[0], appSet, nil, client)
|
g0, err := m.getParams(appSetGenerator.Matrix.Generators[0], appSet, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error failed to get params for first generator in matrix generator: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, a := range g0 {
|
for _, a := range g0 {
|
||||||
g1, err := m.getParams(appSetGenerator.Matrix.Generators[1], appSet, a, client)
|
g1, err := m.getParams(appSetGenerator.Matrix.Generators[1], appSet, a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get params for second generator in the matrix generator: %w", err)
|
return nil, fmt.Errorf("failed to get params for second generator in the matrix generator: %w", err)
|
||||||
}
|
}
|
||||||
for _, b := range g1 {
|
for _, b := range g1 {
|
||||||
|
|
||||||
if appSet.Spec.GoTemplate {
|
if appSet.Spec.GoTemplate {
|
||||||
tmp := map[string]interface{}{}
|
tmp := map[string]interface{}{}
|
||||||
if err := mergo.Merge(&tmp, b, mergo.WithOverride); err != nil {
|
if err := mergo.Merge(&tmp, a); err != nil {
|
||||||
return nil, fmt.Errorf("failed to merge params from the second generator in the matrix generator with temp map: %w", err)
|
return nil, fmt.Errorf("failed to merge params from the first generator in the matrix generator with temp map: %w", err)
|
||||||
}
|
}
|
||||||
if err := mergo.Merge(&tmp, a, mergo.WithOverride); err != nil {
|
if err := mergo.Merge(&tmp, b); err != nil {
|
||||||
return nil, fmt.Errorf("failed to merge params from the second generator in the matrix generator with the first: %w", err)
|
return nil, fmt.Errorf("failed to merge params from the first generator in the matrix generator with the second: %w", err)
|
||||||
}
|
}
|
||||||
res = append(res, tmp)
|
res = append(res, tmp)
|
||||||
} else {
|
} else {
|
||||||
@@ -80,25 +79,28 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, params map[string]interface{}, client client.Client) ([]map[string]interface{}, error) {
|
func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, params map[string]interface{}) ([]map[string]interface{}, error) {
|
||||||
matrixGen, err := getMatrixGenerator(appSetBaseGenerator)
|
var matrix *argoprojiov1alpha1.MatrixGenerator
|
||||||
if err != nil {
|
if appSetBaseGenerator.Matrix != nil {
|
||||||
return nil, err
|
// Since nested matrix generator is represented as a JSON object in the CRD, we unmarshall it back to a Go struct here.
|
||||||
}
|
nestedMatrix, err := argoprojiov1alpha1.ToNestedMatrixGenerator(appSetBaseGenerator.Matrix)
|
||||||
if matrixGen != nil && !appSet.Spec.ApplyNestedSelectors {
|
if err != nil {
|
||||||
foundSelector := dropDisabledNestedSelectors(matrixGen.Generators)
|
return nil, fmt.Errorf("unable to unmarshall nested matrix generator: %v", err)
|
||||||
if foundSelector {
|
}
|
||||||
log.Warnf("AppSet '%v' defines selector on nested matrix generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selectors", appSet.Name)
|
if nestedMatrix != nil {
|
||||||
|
matrix = nestedMatrix.ToMatrixGenerator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mergeGen, err := getMergeGenerator(appSetBaseGenerator)
|
|
||||||
if err != nil {
|
var mergeGenerator *argoprojiov1alpha1.MergeGenerator
|
||||||
return nil, fmt.Errorf("error retrieving merge generator: %w", err)
|
if appSetBaseGenerator.Merge != nil {
|
||||||
}
|
// Since nested merge generator is represented as a JSON object in the CRD, we unmarshall it back to a Go struct here.
|
||||||
if mergeGen != nil && !appSet.Spec.ApplyNestedSelectors {
|
nestedMerge, err := argoprojiov1alpha1.ToNestedMergeGenerator(appSetBaseGenerator.Merge)
|
||||||
foundSelector := dropDisabledNestedSelectors(mergeGen.Generators)
|
if err != nil {
|
||||||
if foundSelector {
|
return nil, fmt.Errorf("unable to unmarshall nested merge generator: %v", err)
|
||||||
log.Warnf("AppSet '%v' defines selector on nested merge generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selectors", appSet.Name)
|
}
|
||||||
|
if nestedMerge != nil {
|
||||||
|
mergeGenerator = nestedMerge.ToMergeGenerator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,18 +112,17 @@ func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Appli
|
|||||||
SCMProvider: appSetBaseGenerator.SCMProvider,
|
SCMProvider: appSetBaseGenerator.SCMProvider,
|
||||||
ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource,
|
ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource,
|
||||||
PullRequest: appSetBaseGenerator.PullRequest,
|
PullRequest: appSetBaseGenerator.PullRequest,
|
||||||
Plugin: appSetBaseGenerator.Plugin,
|
Matrix: matrix,
|
||||||
Matrix: matrixGen,
|
Merge: mergeGenerator,
|
||||||
Merge: mergeGen,
|
|
||||||
Selector: appSetBaseGenerator.Selector,
|
Selector: appSetBaseGenerator.Selector,
|
||||||
},
|
},
|
||||||
m.supportedGenerators,
|
m.supportedGenerators,
|
||||||
argoprojiov1alpha1.ApplicationSetTemplate{},
|
argoprojiov1alpha1.ApplicationSetTemplate{},
|
||||||
appSet,
|
appSet,
|
||||||
params,
|
params)
|
||||||
client)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("child generator returned an error on parameter generation: %w", err)
|
return nil, fmt.Errorf("child generator returned an error on parameter generation: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(t) == 0 {
|
if len(t) == 0 {
|
||||||
@@ -142,18 +143,11 @@ func (m *MatrixGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
|
|||||||
var found bool
|
var found bool
|
||||||
|
|
||||||
for _, r := range appSetGenerator.Matrix.Generators {
|
for _, r := range appSetGenerator.Matrix.Generators {
|
||||||
matrixGen, _ := getMatrixGenerator(r)
|
|
||||||
mergeGen, _ := getMergeGenerator(r)
|
|
||||||
base := &argoprojiov1alpha1.ApplicationSetGenerator{
|
base := &argoprojiov1alpha1.ApplicationSetGenerator{
|
||||||
List: r.List,
|
List: r.List,
|
||||||
Clusters: r.Clusters,
|
Clusters: r.Clusters,
|
||||||
Git: r.Git,
|
Git: r.Git,
|
||||||
PullRequest: r.PullRequest,
|
PullRequest: r.PullRequest,
|
||||||
Plugin: r.Plugin,
|
|
||||||
SCMProvider: r.SCMProvider,
|
|
||||||
ClusterDecisionResource: r.ClusterDecisionResource,
|
|
||||||
Matrix: matrixGen,
|
|
||||||
Merge: mergeGen,
|
|
||||||
}
|
}
|
||||||
generators := GetRelevantGenerators(base, m.supportedGenerators)
|
generators := GetRelevantGenerators(base, m.supportedGenerators)
|
||||||
|
|
||||||
@@ -171,17 +165,7 @@ func (m *MatrixGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
|
|||||||
} else {
|
} else {
|
||||||
return NoRequeueAfter
|
return NoRequeueAfter
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func getMatrixGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MatrixGenerator, error) {
|
|
||||||
if r.Matrix == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
matrix, err := argoprojiov1alpha1.ToNestedMatrixGenerator(r.Matrix)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return matrix.ToMatrixGenerator(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MatrixGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
|
func (m *MatrixGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
@@ -13,17 +12,15 @@ import (
|
|||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||||
|
|
||||||
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
|
|
||||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
|
||||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMatrixGenerate(t *testing.T) {
|
func TestMatrixGenerate(t *testing.T) {
|
||||||
|
|
||||||
gitGenerator := &argoprojiov1alpha1.GitGenerator{
|
gitGenerator := &argoprojiov1alpha1.GitGenerator{
|
||||||
RepoURL: "RepoURL",
|
RepoURL: "RepoURL",
|
||||||
Revision: "Revision",
|
Revision: "Revision",
|
||||||
@@ -31,7 +28,7 @@ func TestMatrixGenerate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
listGenerator := &argoprojiov1alpha1.ListGenerator{
|
listGenerator := &argoprojiov1alpha1.ListGenerator{
|
||||||
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url", "templated": "test-{{path.basenameNormalized}}"}`)}},
|
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url"}`)}},
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@@ -51,8 +48,8 @@ func TestMatrixGenerate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: []map[string]interface{}{
|
expected: []map[string]interface{}{
|
||||||
{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "cluster": "Cluster", "url": "Url", "templated": "test-app1"},
|
{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "cluster": "Cluster", "url": "Url"},
|
||||||
{"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2", "cluster": "Cluster", "url": "Url", "templated": "test-app2"},
|
{"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2", "cluster": "Cluster", "url": "Url"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -147,11 +144,12 @@ func TestMatrixGenerate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, g := range testCaseCopy.baseGenerators {
|
for _, g := range testCaseCopy.baseGenerators {
|
||||||
|
|
||||||
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
|
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
|
||||||
Git: g.Git,
|
Git: g.Git,
|
||||||
List: g.List,
|
List: g.List,
|
||||||
}
|
}
|
||||||
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet, mock.Anything).Return([]map[string]interface{}{
|
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]interface{}{
|
||||||
{
|
{
|
||||||
"path": "app1",
|
"path": "app1",
|
||||||
"path.basename": "app1",
|
"path.basename": "app1",
|
||||||
@@ -168,7 +166,7 @@ func TestMatrixGenerate(t *testing.T) {
|
|||||||
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
|
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
|
||||||
}
|
}
|
||||||
|
|
||||||
matrixGenerator := NewMatrixGenerator(
|
var matrixGenerator = NewMatrixGenerator(
|
||||||
map[string]Generator{
|
map[string]Generator{
|
||||||
"Git": genMock,
|
"Git": genMock,
|
||||||
"List": &ListGenerator{},
|
"List": &ListGenerator{},
|
||||||
@@ -180,19 +178,22 @@ func TestMatrixGenerate(t *testing.T) {
|
|||||||
Generators: testCaseCopy.baseGenerators,
|
Generators: testCaseCopy.baseGenerators,
|
||||||
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
|
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
|
||||||
},
|
},
|
||||||
}, appSet, nil)
|
}, appSet)
|
||||||
|
|
||||||
if testCaseCopy.expectedErr != nil {
|
if testCaseCopy.expectedErr != nil {
|
||||||
require.ErrorIs(t, err, testCaseCopy.expectedErr)
|
assert.ErrorIs(t, err, testCaseCopy.expectedErr)
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, testCaseCopy.expected, got)
|
assert.Equal(t, testCaseCopy.expected, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMatrixGenerateGoTemplate(t *testing.T) {
|
func TestMatrixGenerateGoTemplate(t *testing.T) {
|
||||||
|
|
||||||
gitGenerator := &argoprojiov1alpha1.GitGenerator{
|
gitGenerator := &argoprojiov1alpha1.GitGenerator{
|
||||||
RepoURL: "RepoURL",
|
RepoURL: "RepoURL",
|
||||||
Revision: "Revision",
|
Revision: "Revision",
|
||||||
@@ -267,28 +268,6 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
|
|||||||
{"a": "2", "b": "2"},
|
{"a": "2", "b": "2"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "parameter override: first list elements take precedence",
|
|
||||||
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
|
|
||||||
{
|
|
||||||
List: &argoprojiov1alpha1.ListGenerator{
|
|
||||||
Elements: []apiextensionsv1.JSON{
|
|
||||||
{Raw: []byte(`{"booleanFalse": false, "booleanTrue": true, "stringFalse": "false", "stringTrue": "true"}`)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
List: &argoprojiov1alpha1.ListGenerator{
|
|
||||||
Elements: []apiextensionsv1.JSON{
|
|
||||||
{Raw: []byte(`{"booleanFalse": true, "booleanTrue": false, "stringFalse": "true", "stringTrue": "false"}`)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: []map[string]interface{}{
|
|
||||||
{"booleanFalse": false, "booleanTrue": true, "stringFalse": "false", "stringTrue": "true"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "returns error if there is less than two base generators",
|
name: "returns error if there is less than two base generators",
|
||||||
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
|
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
|
||||||
@@ -356,11 +335,12 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, g := range testCaseCopy.baseGenerators {
|
for _, g := range testCaseCopy.baseGenerators {
|
||||||
|
|
||||||
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
|
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
|
||||||
Git: g.Git,
|
Git: g.Git,
|
||||||
List: g.List,
|
List: g.List,
|
||||||
}
|
}
|
||||||
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet, mock.Anything).Return([]map[string]interface{}{
|
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]interface{}{
|
||||||
{
|
{
|
||||||
"path": map[string]string{
|
"path": map[string]string{
|
||||||
"path": "app1",
|
"path": "app1",
|
||||||
@@ -381,7 +361,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
|
|||||||
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
|
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
|
||||||
}
|
}
|
||||||
|
|
||||||
matrixGenerator := NewMatrixGenerator(
|
var matrixGenerator = NewMatrixGenerator(
|
||||||
map[string]Generator{
|
map[string]Generator{
|
||||||
"Git": genMock,
|
"Git": genMock,
|
||||||
"List": &ListGenerator{},
|
"List": &ListGenerator{},
|
||||||
@@ -393,19 +373,22 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
|
|||||||
Generators: testCaseCopy.baseGenerators,
|
Generators: testCaseCopy.baseGenerators,
|
||||||
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
|
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
|
||||||
},
|
},
|
||||||
}, appSet, nil)
|
}, appSet)
|
||||||
|
|
||||||
if testCaseCopy.expectedErr != nil {
|
if testCaseCopy.expectedErr != nil {
|
||||||
require.ErrorIs(t, err, testCaseCopy.expectedErr)
|
assert.ErrorIs(t, err, testCaseCopy.expectedErr)
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, testCaseCopy.expected, got)
|
assert.Equal(t, testCaseCopy.expected, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMatrixGetRequeueAfter(t *testing.T) {
|
func TestMatrixGetRequeueAfter(t *testing.T) {
|
||||||
|
|
||||||
gitGenerator := &argoprojiov1alpha1.GitGenerator{
|
gitGenerator := &argoprojiov1alpha1.GitGenerator{
|
||||||
RepoURL: "RepoURL",
|
RepoURL: "RepoURL",
|
||||||
Revision: "Revision",
|
Revision: "Revision",
|
||||||
@@ -418,10 +401,6 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
|
|||||||
|
|
||||||
pullRequestGenerator := &argoprojiov1alpha1.PullRequestGenerator{}
|
pullRequestGenerator := &argoprojiov1alpha1.PullRequestGenerator{}
|
||||||
|
|
||||||
scmGenerator := &argoprojiov1alpha1.SCMProviderGenerator{}
|
|
||||||
|
|
||||||
duckTypeGenerator := &argoprojiov1alpha1.DuckTypeGenerator{}
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
|
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
|
||||||
@@ -479,30 +458,6 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expected: time.Duration(30 * time.Minute),
|
expected: time.Duration(30 * time.Minute),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "returns the default time for duck type generator",
|
|
||||||
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
|
|
||||||
{
|
|
||||||
Git: gitGenerator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ClusterDecisionResource: duckTypeGenerator,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: time.Duration(3 * time.Minute),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "returns the default time for scm generator",
|
|
||||||
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
|
|
||||||
{
|
|
||||||
Git: gitGenerator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
SCMProvider: scmGenerator,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: time.Duration(30 * time.Minute),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
@@ -513,22 +468,18 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
|
|||||||
|
|
||||||
for _, g := range testCaseCopy.baseGenerators {
|
for _, g := range testCaseCopy.baseGenerators {
|
||||||
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
|
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
|
||||||
Git: g.Git,
|
Git: g.Git,
|
||||||
List: g.List,
|
List: g.List,
|
||||||
PullRequest: g.PullRequest,
|
PullRequest: g.PullRequest,
|
||||||
SCMProvider: g.SCMProvider,
|
|
||||||
ClusterDecisionResource: g.ClusterDecisionResource,
|
|
||||||
}
|
}
|
||||||
mock.On("GetRequeueAfter", &gitGeneratorSpec).Return(testCaseCopy.gitGetRequeueAfter, nil)
|
mock.On("GetRequeueAfter", &gitGeneratorSpec).Return(testCaseCopy.gitGetRequeueAfter, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
matrixGenerator := NewMatrixGenerator(
|
var matrixGenerator = NewMatrixGenerator(
|
||||||
map[string]Generator{
|
map[string]Generator{
|
||||||
"Git": mock,
|
"Git": mock,
|
||||||
"List": &ListGenerator{},
|
"List": &ListGenerator{},
|
||||||
"PullRequest": &PullRequestGenerator{},
|
"PullRequest": &PullRequestGenerator{},
|
||||||
"SCMProvider": &SCMProviderGenerator{},
|
|
||||||
"ClusterDecisionResource": &DuckTypeGenerator{},
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -540,7 +491,9 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
assert.Equal(t, testCaseCopy.expected, got)
|
assert.Equal(t, testCaseCopy.expected, got)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,8 +531,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,
|
||||||
},
|
},
|
||||||
@@ -645,9 +598,10 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
|
|||||||
fakeClient,
|
fakeClient,
|
||||||
testCase.clientError,
|
testCase.clientError,
|
||||||
}
|
}
|
||||||
clusterGenerator := NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
|
var clusterGenerator = NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
|
||||||
|
|
||||||
for _, g := range testCaseCopy.baseGenerators {
|
for _, g := range testCaseCopy.baseGenerators {
|
||||||
|
|
||||||
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
|
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
|
||||||
Git: g.Git,
|
Git: g.Git,
|
||||||
Clusters: g.Clusters,
|
Clusters: g.Clusters,
|
||||||
@@ -667,7 +621,7 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
|
|||||||
genMock.On("GetTemplate", &gitGeneratorSpec).
|
genMock.On("GetTemplate", &gitGeneratorSpec).
|
||||||
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
|
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
|
||||||
}
|
}
|
||||||
matrixGenerator := NewMatrixGenerator(
|
var matrixGenerator = NewMatrixGenerator(
|
||||||
map[string]Generator{
|
map[string]Generator{
|
||||||
"Git": genMock,
|
"Git": genMock,
|
||||||
"Clusters": clusterGenerator,
|
"Clusters": clusterGenerator,
|
||||||
@@ -679,14 +633,15 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
|
|||||||
Generators: testCaseCopy.baseGenerators,
|
Generators: testCaseCopy.baseGenerators,
|
||||||
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
|
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
|
||||||
},
|
},
|
||||||
}, appSet, nil)
|
}, appSet)
|
||||||
|
|
||||||
if testCaseCopy.expectedErr != nil {
|
if testCaseCopy.expectedErr != nil {
|
||||||
require.ErrorIs(t, err, testCaseCopy.expectedErr)
|
assert.ErrorIs(t, err, testCaseCopy.expectedErr)
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, testCaseCopy.expected, got)
|
assert.Equal(t, testCaseCopy.expected, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -734,7 +689,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 +705,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",
|
||||||
@@ -828,14 +781,16 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
|
|||||||
fakeClient,
|
fakeClient,
|
||||||
testCase.clientError,
|
testCase.clientError,
|
||||||
}
|
}
|
||||||
clusterGenerator := NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
|
var clusterGenerator = NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
|
||||||
|
|
||||||
for _, g := range testCaseCopy.baseGenerators {
|
for _, g := range testCaseCopy.baseGenerators {
|
||||||
|
|
||||||
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
|
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
|
||||||
Git: g.Git,
|
Git: g.Git,
|
||||||
Clusters: g.Clusters,
|
Clusters: g.Clusters,
|
||||||
}
|
}
|
||||||
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]interface{}{
|
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]interface{}{
|
||||||
|
|
||||||
{
|
{
|
||||||
"path": map[string]string{
|
"path": map[string]string{
|
||||||
"path": "examples/git-generator-files-discovery/cluster-config/dev/config.json",
|
"path": "examples/git-generator-files-discovery/cluster-config/dev/config.json",
|
||||||
@@ -854,7 +809,7 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
|
|||||||
genMock.On("GetTemplate", &gitGeneratorSpec).
|
genMock.On("GetTemplate", &gitGeneratorSpec).
|
||||||
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
|
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
|
||||||
}
|
}
|
||||||
matrixGenerator := NewMatrixGenerator(
|
var matrixGenerator = NewMatrixGenerator(
|
||||||
map[string]Generator{
|
map[string]Generator{
|
||||||
"Git": genMock,
|
"Git": genMock,
|
||||||
"Clusters": clusterGenerator,
|
"Clusters": clusterGenerator,
|
||||||
@@ -866,176 +821,17 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
|
|||||||
Generators: testCaseCopy.baseGenerators,
|
Generators: testCaseCopy.baseGenerators,
|
||||||
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
|
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
|
||||||
},
|
},
|
||||||
}, appSet, nil)
|
}, appSet)
|
||||||
|
|
||||||
if testCaseCopy.expectedErr != nil {
|
if testCaseCopy.expectedErr != nil {
|
||||||
require.ErrorIs(t, err, testCaseCopy.expectedErr)
|
assert.ErrorIs(t, err, testCaseCopy.expectedErr)
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, testCaseCopy.expected, got)
|
assert.Equal(t, testCaseCopy.expected, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMatrixGenerateListElementsYaml(t *testing.T) {
|
|
||||||
gitGenerator := &argoprojiov1alpha1.GitGenerator{
|
|
||||||
RepoURL: "RepoURL",
|
|
||||||
Revision: "Revision",
|
|
||||||
Files: []argoprojiov1alpha1.GitFileGeneratorItem{
|
|
||||||
{Path: "config.yaml"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
listGenerator := &argoprojiov1alpha1.ListGenerator{
|
|
||||||
Elements: []apiextensionsv1.JSON{},
|
|
||||||
ElementsYaml: "{{ .foo.bar | toJson }}",
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
|
|
||||||
expectedErr error
|
|
||||||
expected []map[string]interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "happy flow - generate params",
|
|
||||||
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
|
|
||||||
{
|
|
||||||
Git: gitGenerator,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
List: listGenerator,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"chart": "a",
|
|
||||||
"version": "1",
|
|
||||||
"foo": map[string]interface{}{
|
|
||||||
"bar": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"chart": "a",
|
|
||||||
"version": "1",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"chart": "b",
|
|
||||||
"version": "2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"path": map[string]interface{}{
|
|
||||||
"basename": "dir",
|
|
||||||
"basenameNormalized": "dir",
|
|
||||||
"filename": "file_name.yaml",
|
|
||||||
"filenameNormalized": "file-name.yaml",
|
|
||||||
"path": "path/dir",
|
|
||||||
"segments": []string{
|
|
||||||
"path",
|
|
||||||
"dir",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"chart": "b",
|
|
||||||
"version": "2",
|
|
||||||
"foo": map[string]interface{}{
|
|
||||||
"bar": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"chart": "a",
|
|
||||||
"version": "1",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"chart": "b",
|
|
||||||
"version": "2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"path": map[string]interface{}{
|
|
||||||
"basename": "dir",
|
|
||||||
"basenameNormalized": "dir",
|
|
||||||
"filename": "file_name.yaml",
|
|
||||||
"filenameNormalized": "file-name.yaml",
|
|
||||||
"path": "path/dir",
|
|
||||||
"segments": []string{
|
|
||||||
"path",
|
|
||||||
"dir",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
testCaseCopy := testCase // Since tests may run in parallel
|
|
||||||
|
|
||||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
|
||||||
genMock := &generatorMock{}
|
|
||||||
appSet := &argoprojiov1alpha1.ApplicationSet{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "set",
|
|
||||||
},
|
|
||||||
Spec: argoprojiov1alpha1.ApplicationSetSpec{
|
|
||||||
GoTemplate: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, g := range testCaseCopy.baseGenerators {
|
|
||||||
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
|
|
||||||
Git: g.Git,
|
|
||||||
List: g.List,
|
|
||||||
}
|
|
||||||
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]any{{
|
|
||||||
"foo": map[string]interface{}{
|
|
||||||
"bar": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"chart": "a",
|
|
||||||
"version": "1",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"chart": "b",
|
|
||||||
"version": "2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"path": map[string]interface{}{
|
|
||||||
"basename": "dir",
|
|
||||||
"basenameNormalized": "dir",
|
|
||||||
"filename": "file_name.yaml",
|
|
||||||
"filenameNormalized": "file-name.yaml",
|
|
||||||
"path": "path/dir",
|
|
||||||
"segments": []string{
|
|
||||||
"path",
|
|
||||||
"dir",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}, nil)
|
|
||||||
genMock.On("GetTemplate", &gitGeneratorSpec).
|
|
||||||
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
|
|
||||||
}
|
|
||||||
|
|
||||||
matrixGenerator := NewMatrixGenerator(
|
|
||||||
map[string]Generator{
|
|
||||||
"Git": genMock,
|
|
||||||
"List": &ListGenerator{},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
got, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
|
|
||||||
Matrix: &argoprojiov1alpha1.MatrixGenerator{
|
|
||||||
Generators: testCaseCopy.baseGenerators,
|
|
||||||
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
|
|
||||||
},
|
|
||||||
}, appSet, nil)
|
|
||||||
|
|
||||||
if testCaseCopy.expectedErr != nil {
|
|
||||||
require.ErrorIs(t, err, testCaseCopy.expectedErr)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, testCaseCopy.expected, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1049,7 +845,7 @@ func (g *generatorMock) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applicat
|
|||||||
return args.Get(0).(*argoprojiov1alpha1.ApplicationSetTemplate)
|
return args.Get(0).(*argoprojiov1alpha1.ApplicationSetTemplate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *generatorMock) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
|
func (g *generatorMock) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
|
||||||
args := g.Called(appSetGenerator, appSet)
|
args := g.Called(appSetGenerator, appSet)
|
||||||
|
|
||||||
return args.Get(0).([]map[string]interface{}), args.Error(1)
|
return args.Get(0).([]map[string]interface{}), args.Error(1)
|
||||||
@@ -1059,81 +855,5 @@ func (g *generatorMock) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Appl
|
|||||||
args := g.Called(appSetGenerator)
|
args := g.Called(appSetGenerator)
|
||||||
|
|
||||||
return args.Get(0).(time.Duration)
|
return args.Get(0).(time.Duration)
|
||||||
}
|
|
||||||
|
|
||||||
func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
|
|
||||||
// Given a matrix generator over a list generator and a git files generator, the nested git files generator should
|
|
||||||
// be treated as a files generator, and it should produce parameters.
|
|
||||||
|
|
||||||
// This tests for a specific bug where a nested git files generator was being treated as a directory generator. This
|
|
||||||
// happened because, when the matrix generator was being processed, the nested git files generator was being
|
|
||||||
// interpolated by the deeplyReplace function. That function cannot differentiate between a nil slice and an empty
|
|
||||||
// slice. So it was replacing the `Directories` field with an empty slice, which the ApplicationSet controller
|
|
||||||
// interpreted as meaning this was a directory generator, not a files generator.
|
|
||||||
|
|
||||||
// Now instead of checking for nil, we check whether the field is a non-empty slice. This test prevents a regression
|
|
||||||
// of that bug.
|
|
||||||
|
|
||||||
listGeneratorMock := &generatorMock{}
|
|
||||||
listGeneratorMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).Return([]map[string]interface{}{
|
|
||||||
{"some": "value"},
|
|
||||||
}, nil)
|
|
||||||
listGeneratorMock.On("GetTemplate", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator")).Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
|
|
||||||
|
|
||||||
gitGeneratorSpec := &argoprojiov1alpha1.GitGenerator{
|
|
||||||
RepoURL: "https://git.example.com",
|
|
||||||
Files: []argoprojiov1alpha1.GitFileGeneratorItem{
|
|
||||||
{Path: "some/path.json"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
repoServiceMock := &mocks.Repos{}
|
|
||||||
repoServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(map[string][]byte{
|
|
||||||
"some/path.json": []byte("test: content"),
|
|
||||||
}, nil)
|
|
||||||
gitGenerator := NewGitGenerator(repoServiceMock, "")
|
|
||||||
|
|
||||||
matrixGenerator := NewMatrixGenerator(map[string]Generator{
|
|
||||||
"List": listGeneratorMock,
|
|
||||||
"Git": gitGenerator,
|
|
||||||
})
|
|
||||||
|
|
||||||
matrixGeneratorSpec := &argoprojiov1alpha1.MatrixGenerator{
|
|
||||||
Generators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
|
|
||||||
{
|
|
||||||
List: &argoprojiov1alpha1.ListGenerator{
|
|
||||||
Elements: []apiextensionsv1.JSON{
|
|
||||||
{
|
|
||||||
Raw: []byte(`{"some": "value"}`),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Git: gitGeneratorSpec,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
scheme := runtime.NewScheme()
|
|
||||||
err := v1alpha1.AddToScheme(scheme)
|
|
||||||
require.NoError(t, err)
|
|
||||||
appProject := argoprojiov1alpha1.AppProject{}
|
|
||||||
|
|
||||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
|
|
||||||
|
|
||||||
params, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
|
|
||||||
Matrix: matrixGeneratorSpec,
|
|
||||||
}, &argoprojiov1alpha1.ApplicationSet{}, client)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, []map[string]interface{}{{
|
|
||||||
"path": "some",
|
|
||||||
"path.basename": "some",
|
|
||||||
"path.basenameNormalized": "some",
|
|
||||||
"path.filename": "path.json",
|
|
||||||
"path.filenameNormalized": "path.json",
|
|
||||||
"path[0]": "some",
|
|
||||||
"some": "value",
|
|
||||||
"test": "content",
|
|
||||||
}}, params)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
|
|
||||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
"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"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Generator = (*MergeGenerator)(nil)
|
var _ Generator = (*MergeGenerator)(nil)
|
||||||
@@ -37,12 +34,12 @@ func NewMergeGenerator(supportedGenerators map[string]Generator) Generator {
|
|||||||
|
|
||||||
// getParamSetsForAllGenerators generates params for each child generator in a MergeGenerator. Param sets are returned
|
// getParamSetsForAllGenerators generates params for each child generator in a MergeGenerator. Param sets are returned
|
||||||
// in slices ordered according to the order of the given generators.
|
// in slices ordered according to the order of the given generators.
|
||||||
func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([][]map[string]interface{}, error) {
|
func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([][]map[string]interface{}, error) {
|
||||||
var paramSets [][]map[string]interface{}
|
var paramSets [][]map[string]interface{}
|
||||||
for i, generator := range generators {
|
for _, generator := range generators {
|
||||||
generatorParamSets, err := m.getParams(generator, appSet, client)
|
generatorParamSets, err := m.getParams(generator, appSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting params from generator %d of %d: %w", i+1, len(generators), err)
|
return nil, err
|
||||||
}
|
}
|
||||||
// concatenate param lists produced by each generator
|
// concatenate param lists produced by each generator
|
||||||
paramSets = append(paramSets, generatorParamSets)
|
paramSets = append(paramSets, generatorParamSets)
|
||||||
@@ -51,7 +48,7 @@ func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1a
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GenerateParams gets the params produced by the MergeGenerator.
|
// GenerateParams gets the params produced by the MergeGenerator.
|
||||||
func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error) {
|
func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
|
||||||
if appSetGenerator.Merge == nil {
|
if appSetGenerator.Merge == nil {
|
||||||
return nil, EmptyAppSetGeneratorError
|
return nil, EmptyAppSetGeneratorError
|
||||||
}
|
}
|
||||||
@@ -60,33 +57,34 @@ func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appl
|
|||||||
return nil, ErrLessThanTwoGeneratorsInMerge
|
return nil, ErrLessThanTwoGeneratorsInMerge
|
||||||
}
|
}
|
||||||
|
|
||||||
paramSetsFromGenerators, err := m.getParamSetsForAllGenerators(appSetGenerator.Merge.Generators, appSet, client)
|
paramSetsFromGenerators, err := m.getParamSetsForAllGenerators(appSetGenerator.Merge.Generators, appSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting param sets from generators: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
baseParamSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSetsFromGenerators[0])
|
baseParamSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSetsFromGenerators[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting param sets by merge key: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, paramSets := range paramSetsFromGenerators[1:] {
|
for _, paramSets := range paramSetsFromGenerators[1:] {
|
||||||
paramSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSets)
|
paramSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting param sets by merge key: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for mergeKeyValue, baseParamSet := range baseParamSetsByMergeKey {
|
for mergeKeyValue, baseParamSet := range baseParamSetsByMergeKey {
|
||||||
if overrideParamSet, exists := paramSetsByMergeKey[mergeKeyValue]; exists {
|
if overrideParamSet, exists := paramSetsByMergeKey[mergeKeyValue]; exists {
|
||||||
|
|
||||||
if appSet.Spec.GoTemplate {
|
if appSet.Spec.GoTemplate {
|
||||||
if err := mergo.Merge(&baseParamSet, overrideParamSet, mergo.WithOverride); err != nil {
|
if err := mergo.Merge(&baseParamSet, overrideParamSet, mergo.WithOverride); err != nil {
|
||||||
return nil, fmt.Errorf("error merging base param set with override param set: %w", err)
|
return nil, fmt.Errorf("failed to merge base param set with override param set: %w", err)
|
||||||
}
|
}
|
||||||
baseParamSetsByMergeKey[mergeKeyValue] = baseParamSet
|
baseParamSetsByMergeKey[mergeKeyValue] = baseParamSet
|
||||||
} else {
|
} else {
|
||||||
overriddenParamSet, err := utils.CombineStringMapsAllowDuplicates(baseParamSet, overrideParamSet)
|
overriddenParamSet, err := utils.CombineStringMapsAllowDuplicates(baseParamSet, overrideParamSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error combining string maps: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
baseParamSetsByMergeKey[mergeKeyValue] = utils.ConvertToMapStringInterface(overriddenParamSet)
|
baseParamSetsByMergeKey[mergeKeyValue] = utils.ConvertToMapStringInterface(overriddenParamSet)
|
||||||
}
|
}
|
||||||
@@ -95,7 +93,7 @@ func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appl
|
|||||||
}
|
}
|
||||||
|
|
||||||
mergedParamSets := make([]map[string]interface{}, len(baseParamSetsByMergeKey))
|
mergedParamSets := make([]map[string]interface{}, len(baseParamSetsByMergeKey))
|
||||||
i := 0
|
var i = 0
|
||||||
for _, mergedParamSet := range baseParamSetsByMergeKey {
|
for _, mergedParamSet := range baseParamSetsByMergeKey {
|
||||||
mergedParamSets[i] = mergedParamSet
|
mergedParamSets[i] = mergedParamSet
|
||||||
i += 1
|
i += 1
|
||||||
@@ -125,7 +123,7 @@ func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]interface
|
|||||||
}
|
}
|
||||||
paramSetKeyJson, err := json.Marshal(paramSetKey)
|
paramSetKeyJson, err := json.Marshal(paramSetKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error marshalling param set key json: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
paramSetKeyString := string(paramSetKeyJson)
|
paramSetKeyString := string(paramSetKeyJson)
|
||||||
if _, exists := paramSetsByMergeKey[paramSetKeyString]; exists {
|
if _, exists := paramSetsByMergeKey[paramSetKeyString]; exists {
|
||||||
@@ -138,25 +136,27 @@ func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]interface
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getParams get the parameters generated by this generator.
|
// getParams get the parameters generated by this generator.
|
||||||
func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error) {
|
func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
|
||||||
matrixGen, err := getMatrixGenerator(appSetBaseGenerator)
|
|
||||||
if err != nil {
|
var matrix *argoprojiov1alpha1.MatrixGenerator
|
||||||
return nil, err
|
if appSetBaseGenerator.Matrix != nil {
|
||||||
}
|
nestedMatrix, err := argoprojiov1alpha1.ToNestedMatrixGenerator(appSetBaseGenerator.Matrix)
|
||||||
if matrixGen != nil && !appSet.Spec.ApplyNestedSelectors {
|
if err != nil {
|
||||||
foundSelector := dropDisabledNestedSelectors(matrixGen.Generators)
|
return nil, err
|
||||||
if foundSelector {
|
}
|
||||||
log.Warnf("AppSet '%v' defines selector on nested matrix generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selector", appSet.Name)
|
if nestedMatrix != nil {
|
||||||
|
matrix = nestedMatrix.ToMatrixGenerator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mergeGen, err := getMergeGenerator(appSetBaseGenerator)
|
|
||||||
if err != nil {
|
var mergeGenerator *argoprojiov1alpha1.MergeGenerator
|
||||||
return nil, err
|
if appSetBaseGenerator.Merge != nil {
|
||||||
}
|
nestedMerge, err := argoprojiov1alpha1.ToNestedMergeGenerator(appSetBaseGenerator.Merge)
|
||||||
if mergeGen != nil && !appSet.Spec.ApplyNestedSelectors {
|
if err != nil {
|
||||||
foundSelector := dropDisabledNestedSelectors(mergeGen.Generators)
|
return nil, err
|
||||||
if foundSelector {
|
}
|
||||||
log.Warnf("AppSet '%v' defines selector on nested merge generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selector", appSet.Name)
|
if nestedMerge != nil {
|
||||||
|
mergeGenerator = nestedMerge.ToMergeGenerator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,17 +168,17 @@ func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Applic
|
|||||||
SCMProvider: appSetBaseGenerator.SCMProvider,
|
SCMProvider: appSetBaseGenerator.SCMProvider,
|
||||||
ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource,
|
ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource,
|
||||||
PullRequest: appSetBaseGenerator.PullRequest,
|
PullRequest: appSetBaseGenerator.PullRequest,
|
||||||
Plugin: appSetBaseGenerator.Plugin,
|
Matrix: matrix,
|
||||||
Matrix: matrixGen,
|
Merge: mergeGenerator,
|
||||||
Merge: mergeGen,
|
|
||||||
Selector: appSetBaseGenerator.Selector,
|
Selector: appSetBaseGenerator.Selector,
|
||||||
},
|
},
|
||||||
m.supportedGenerators,
|
m.supportedGenerators,
|
||||||
argoprojiov1alpha1.ApplicationSetTemplate{},
|
argoprojiov1alpha1.ApplicationSetTemplate{},
|
||||||
appSet,
|
appSet,
|
||||||
map[string]interface{}{}, client)
|
map[string]interface{}{})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("child generator returned an error on parameter generation: %w", err)
|
return nil, fmt.Errorf("child generator returned an error on parameter generation: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(t) == 0 {
|
if len(t) == 0 {
|
||||||
@@ -197,18 +197,10 @@ func (m *MergeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.App
|
|||||||
var found bool
|
var found bool
|
||||||
|
|
||||||
for _, r := range appSetGenerator.Merge.Generators {
|
for _, r := range appSetGenerator.Merge.Generators {
|
||||||
matrixGen, _ := getMatrixGenerator(r)
|
|
||||||
mergeGen, _ := getMergeGenerator(r)
|
|
||||||
base := &argoprojiov1alpha1.ApplicationSetGenerator{
|
base := &argoprojiov1alpha1.ApplicationSetGenerator{
|
||||||
List: r.List,
|
List: r.List,
|
||||||
Clusters: r.Clusters,
|
Clusters: r.Clusters,
|
||||||
Git: r.Git,
|
Git: r.Git,
|
||||||
PullRequest: r.PullRequest,
|
|
||||||
Plugin: r.Plugin,
|
|
||||||
SCMProvider: r.SCMProvider,
|
|
||||||
ClusterDecisionResource: r.ClusterDecisionResource,
|
|
||||||
Matrix: matrixGen,
|
|
||||||
Merge: mergeGen,
|
|
||||||
}
|
}
|
||||||
generators := GetRelevantGenerators(base, m.supportedGenerators)
|
generators := GetRelevantGenerators(base, m.supportedGenerators)
|
||||||
|
|
||||||
@@ -226,17 +218,7 @@ func (m *MergeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.App
|
|||||||
} else {
|
} else {
|
||||||
return NoRequeueAfter
|
return NoRequeueAfter
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func getMergeGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MergeGenerator, error) {
|
|
||||||
if r.Merge == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
merge, err := argoprojiov1alpha1.ToNestedMergeGenerator(r.Merge)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error converting to nested merge generator: %w", err)
|
|
||||||
}
|
|
||||||
return merge.ToMergeGenerator(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTemplate gets the Template field for the MergeGenerator.
|
// GetTemplate gets the Template field for the MergeGenerator.
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
|
|
||||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||||
@@ -50,6 +49,7 @@ func listOfMapsToSet(maps []map[string]interface{}) (map[string]bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeGenerate(t *testing.T) {
|
func TestMergeGenerate(t *testing.T) {
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
|
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
|
||||||
@@ -156,7 +156,7 @@ func TestMergeGenerate(t *testing.T) {
|
|||||||
|
|
||||||
appSet := &argoprojiov1alpha1.ApplicationSet{}
|
appSet := &argoprojiov1alpha1.ApplicationSet{}
|
||||||
|
|
||||||
mergeGenerator := NewMergeGenerator(
|
var mergeGenerator = NewMergeGenerator(
|
||||||
map[string]Generator{
|
map[string]Generator{
|
||||||
"List": &ListGenerator{},
|
"List": &ListGenerator{},
|
||||||
"Matrix": &MatrixGenerator{
|
"Matrix": &MatrixGenerator{
|
||||||
@@ -178,18 +178,18 @@ func TestMergeGenerate(t *testing.T) {
|
|||||||
MergeKeys: testCaseCopy.mergeKeys,
|
MergeKeys: testCaseCopy.mergeKeys,
|
||||||
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
|
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
|
||||||
},
|
},
|
||||||
}, appSet, nil)
|
}, appSet)
|
||||||
|
|
||||||
if testCaseCopy.expectedErr != nil {
|
if testCaseCopy.expectedErr != nil {
|
||||||
require.EqualError(t, err, testCaseCopy.expectedErr.Error())
|
assert.EqualError(t, err, testCaseCopy.expectedErr.Error())
|
||||||
} else {
|
} else {
|
||||||
expectedSet, err := listOfMapsToSet(testCaseCopy.expected)
|
expectedSet, err := listOfMapsToSet(testCaseCopy.expected)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
actualSet, err := listOfMapsToSet(got)
|
actualSet, err := listOfMapsToSet(got)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, expectedSet, actualSet)
|
assert.Equal(t, expectedSet, actualSet)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -197,7 +197,7 @@ 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)
|
||||||
@@ -339,11 +339,13 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
|
|||||||
got, err := getParamSetsByMergeKey(testCaseCopy.mergeKeys, testCaseCopy.paramSets)
|
got, err := getParamSetsByMergeKey(testCaseCopy.mergeKeys, testCaseCopy.paramSets)
|
||||||
|
|
||||||
if testCaseCopy.expectedErr != nil {
|
if testCaseCopy.expectedErr != nil {
|
||||||
require.EqualError(t, err, testCaseCopy.expectedErr.Error())
|
assert.EqualError(t, err, testCaseCopy.expectedErr.Error())
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, testCaseCopy.expected, got)
|
assert.Equal(t, testCaseCopy.expected, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
|
||||||
|
|
||||||
package mocks
|
|
||||||
|
|
||||||
import (
|
|
||||||
client "sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
|
|
||||||
mock "github.com/stretchr/testify/mock"
|
|
||||||
|
|
||||||
time "time"
|
|
||||||
|
|
||||||
v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Generator is an autogenerated mock type for the Generator type
|
|
||||||
type Generator struct {
|
|
||||||
mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateParams provides a mock function with given fields: appSetGenerator, applicationSetInfo, _a2
|
|
||||||
func (_m *Generator) GenerateParams(appSetGenerator *v1alpha1.ApplicationSetGenerator, applicationSetInfo *v1alpha1.ApplicationSet, _a2 client.Client) ([]map[string]interface{}, error) {
|
|
||||||
ret := _m.Called(appSetGenerator, applicationSetInfo, _a2)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for GenerateParams")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 []map[string]interface{}
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) ([]map[string]interface{}, error)); ok {
|
|
||||||
return rf(appSetGenerator, applicationSetInfo, _a2)
|
|
||||||
}
|
|
||||||
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) []map[string]interface{}); ok {
|
|
||||||
r0 = rf(appSetGenerator, applicationSetInfo, _a2)
|
|
||||||
} else {
|
|
||||||
if ret.Get(0) != nil {
|
|
||||||
r0 = ret.Get(0).([]map[string]interface{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) error); ok {
|
|
||||||
r1 = rf(appSetGenerator, applicationSetInfo, _a2)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRequeueAfter provides a mock function with given fields: appSetGenerator
|
|
||||||
func (_m *Generator) GetRequeueAfter(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration {
|
|
||||||
ret := _m.Called(appSetGenerator)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for GetRequeueAfter")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 time.Duration
|
|
||||||
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) time.Duration); ok {
|
|
||||||
r0 = rf(appSetGenerator)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(time.Duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTemplate provides a mock function with given fields: appSetGenerator
|
|
||||||
func (_m *Generator) GetTemplate(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate {
|
|
||||||
ret := _m.Called(appSetGenerator)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for GetTemplate")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 *v1alpha1.ApplicationSetTemplate
|
|
||||||
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate); ok {
|
|
||||||
r0 = rf(appSetGenerator)
|
|
||||||
} else {
|
|
||||||
if ret.Get(0) != nil {
|
|
||||||
r0 = ret.Get(0).(*v1alpha1.ApplicationSetTemplate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGenerator creates a new instance of Generator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
|
||||||
// The first argument is typically a *testing.T value.
|
|
||||||
func NewGenerator(t interface {
|
|
||||||
mock.TestingT
|
|
||||||
Cleanup(func())
|
|
||||||
}) *Generator {
|
|
||||||
mock := &Generator{}
|
|
||||||
mock.Mock.Test(t)
|
|
||||||
|
|
||||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
|
||||||
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
package generators
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jeremywohl/flatten"
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/client-go/kubernetes"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
|
|
||||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
|
||||||
"github.com/argoproj/argo-cd/v2/util/settings"
|
|
||||||
|
|
||||||
"github.com/argoproj/argo-cd/v2/applicationset/services/plugin"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DefaultPluginRequeueAfterSeconds = 30 * time.Minute
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ Generator = (*PluginGenerator)(nil)
|
|
||||||
|
|
||||||
type PluginGenerator struct {
|
|
||||||
client client.Client
|
|
||||||
ctx context.Context
|
|
||||||
clientset kubernetes.Interface
|
|
||||||
namespace string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPluginGenerator(client client.Client, ctx context.Context, clientset kubernetes.Interface, namespace string) Generator {
|
|
||||||
g := &PluginGenerator{
|
|
||||||
client: client,
|
|
||||||
ctx: ctx,
|
|
||||||
clientset: clientset,
|
|
||||||
namespace: namespace,
|
|
||||||
}
|
|
||||||
return g
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *PluginGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
|
|
||||||
// Return a requeue default of 30 minutes, if no default is specified.
|
|
||||||
|
|
||||||
if appSetGenerator.Plugin.RequeueAfterSeconds != nil {
|
|
||||||
return time.Duration(*appSetGenerator.Plugin.RequeueAfterSeconds) * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
return DefaultPluginRequeueAfterSeconds
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *PluginGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
|
|
||||||
return &appSetGenerator.Plugin.Template
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *PluginGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
|
|
||||||
if appSetGenerator == nil {
|
|
||||||
return nil, EmptyAppSetGeneratorError
|
|
||||||
}
|
|
||||||
|
|
||||||
if appSetGenerator.Plugin == nil {
|
|
||||||
return nil, EmptyAppSetGeneratorError
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
providerConfig := appSetGenerator.Plugin
|
|
||||||
|
|
||||||
pluginClient, err := g.getPluginFromGenerator(ctx, applicationSetInfo.Name, providerConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error getting plugin from generator: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
list, err := pluginClient.List(ctx, providerConfig.Input.Parameters)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error listing params: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := g.generateParams(appSetGenerator, applicationSetInfo, list.Output.Parameters, appSetGenerator.Plugin.Input.Parameters, applicationSetInfo.Spec.GoTemplate)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating params: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *PluginGenerator) getPluginFromGenerator(ctx context.Context, appSetName string, generatorConfig *argoprojiov1alpha1.PluginGenerator) (*plugin.Service, error) {
|
|
||||||
cm, err := g.getConfigMap(ctx, generatorConfig.ConfigMapRef.Name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error fetching ConfigMap: %w", err)
|
|
||||||
}
|
|
||||||
token, err := g.getToken(ctx, cm["token"])
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error fetching Secret token: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var requestTimeout int
|
|
||||||
requestTimeoutStr, ok := cm["requestTimeout"]
|
|
||||||
if ok {
|
|
||||||
requestTimeout, err = strconv.Atoi(requestTimeoutStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error set requestTimeout : %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginClient, err := plugin.NewPluginService(ctx, appSetName, cm["baseUrl"], token, requestTimeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error initializing plugin client: %w", err)
|
|
||||||
}
|
|
||||||
return pluginClient, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *PluginGenerator) generateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, objectsFound []map[string]interface{}, pluginParams argoprojiov1alpha1.PluginParameters, useGoTemplate bool) ([]map[string]interface{}, error) {
|
|
||||||
res := []map[string]interface{}{}
|
|
||||||
|
|
||||||
for _, objectFound := range objectsFound {
|
|
||||||
params := map[string]interface{}{}
|
|
||||||
|
|
||||||
if useGoTemplate {
|
|
||||||
for k, v := range objectFound {
|
|
||||||
params[k] = v
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
flat, err := flatten.Flatten(objectFound, "", flatten.DotStyle)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for k, v := range flat {
|
|
||||||
params[k] = fmt.Sprintf("%v", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
params["generator"] = map[string]interface{}{
|
|
||||||
"input": map[string]argoprojiov1alpha1.PluginParameters{
|
|
||||||
"parameters": pluginParams,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := appendTemplatedValues(appSetGenerator.Plugin.Values, params, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res = append(res, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *PluginGenerator) getToken(ctx context.Context, tokenRef string) (string, error) {
|
|
||||||
if tokenRef == "" || !strings.HasPrefix(tokenRef, "$") {
|
|
||||||
return "", fmt.Errorf("token is empty, or does not reference a secret key starting with '$': %v", tokenRef)
|
|
||||||
}
|
|
||||||
|
|
||||||
secretName, tokenKey := plugin.ParseSecretKey(tokenRef)
|
|
||||||
|
|
||||||
secret := &corev1.Secret{}
|
|
||||||
err := g.client.Get(
|
|
||||||
ctx,
|
|
||||||
client.ObjectKey{
|
|
||||||
Name: secretName,
|
|
||||||
Namespace: g.namespace,
|
|
||||||
},
|
|
||||||
secret)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("error fetching secret %s/%s: %w", g.namespace, secretName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
secretValues := make(map[string]string, len(secret.Data))
|
|
||||||
|
|
||||||
for k, v := range secret.Data {
|
|
||||||
secretValues[k] = string(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
token := settings.ReplaceStringSecret(tokenKey, secretValues)
|
|
||||||
|
|
||||||
return token, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *PluginGenerator) getConfigMap(ctx context.Context, configMapRef string) (map[string]string, error) {
|
|
||||||
cm := &corev1.ConfigMap{}
|
|
||||||
err := g.client.Get(
|
|
||||||
ctx,
|
|
||||||
client.ObjectKey{
|
|
||||||
Name: configMapRef,
|
|
||||||
Namespace: g.namespace,
|
|
||||||
},
|
|
||||||
cm)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
baseUrl, ok := cm.Data["baseUrl"]
|
|
||||||
if !ok || baseUrl == "" {
|
|
||||||
return nil, fmt.Errorf("baseUrl not found in ConfigMap")
|
|
||||||
}
|
|
||||||
|
|
||||||
token, ok := cm.Data["token"]
|
|
||||||
if !ok || token == "" {
|
|
||||||
return nil, fmt.Errorf("token not found in ConfigMap")
|
|
||||||
}
|
|
||||||
|
|
||||||
return cm.Data, nil
|
|
||||||
}
|
|
||||||
@@ -1,701 +0,0 @@
|
|||||||
package generators
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
kubefake "k8s.io/client-go/kubernetes/fake"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
||||||
|
|
||||||
"github.com/argoproj/argo-cd/v2/applicationset/services/plugin"
|
|
||||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPluginGenerateParams(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
configmap *v1.ConfigMap
|
|
||||||
secret *v1.Secret
|
|
||||||
inputParameters map[string]apiextensionsv1.JSON
|
|
||||||
values map[string]string
|
|
||||||
gotemplate bool
|
|
||||||
expected []map[string]interface{}
|
|
||||||
content []byte
|
|
||||||
expectedError error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "simple case",
|
|
||||||
configmap: &v1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "first-plugin-cm",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string]string{
|
|
||||||
"baseUrl": "http://127.0.0.1",
|
|
||||||
"token": "$plugin.token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: &v1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "argocd-secret",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{
|
|
||||||
"plugin.token": []byte("my-secret"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputParameters: map[string]apiextensionsv1.JSON{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
gotemplate: false,
|
|
||||||
content: []byte(`{"output": {
|
|
||||||
"parameters": [{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2": {
|
|
||||||
"key2_1": "val2_1",
|
|
||||||
"key2_2": {
|
|
||||||
"key2_2_1": "val2_2_1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"key3": 123
|
|
||||||
}]
|
|
||||||
}}`),
|
|
||||||
expected: []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2.key2_1": "val2_1",
|
|
||||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
|
||||||
"key3": "123",
|
|
||||||
"generator": map[string]interface{}{
|
|
||||||
"input": argoprojiov1alpha1.PluginInput{
|
|
||||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedError: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "simple case with values",
|
|
||||||
configmap: &v1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "first-plugin-cm",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string]string{
|
|
||||||
"baseUrl": "http://127.0.0.1",
|
|
||||||
"token": "$plugin.token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: &v1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "argocd-secret",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{
|
|
||||||
"plugin.token": []byte("my-secret"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputParameters: map[string]apiextensionsv1.JSON{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
values: map[string]string{
|
|
||||||
"valuekey1": "valuevalue1",
|
|
||||||
"valuekey2": "templated-{{key1}}",
|
|
||||||
},
|
|
||||||
gotemplate: false,
|
|
||||||
content: []byte(`{"output": {
|
|
||||||
"parameters": [{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2": {
|
|
||||||
"key2_1": "val2_1",
|
|
||||||
"key2_2": {
|
|
||||||
"key2_2_1": "val2_2_1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"key3": 123
|
|
||||||
}]
|
|
||||||
}}`),
|
|
||||||
expected: []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2.key2_1": "val2_1",
|
|
||||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
|
||||||
"key3": "123",
|
|
||||||
"values.valuekey1": "valuevalue1",
|
|
||||||
"values.valuekey2": "templated-val1",
|
|
||||||
"generator": map[string]interface{}{
|
|
||||||
"input": argoprojiov1alpha1.PluginInput{
|
|
||||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedError: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "simple case with gotemplate",
|
|
||||||
configmap: &v1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "first-plugin-cm",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string]string{
|
|
||||||
"baseUrl": "http://127.0.0.1",
|
|
||||||
"token": "$plugin.token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: &v1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "argocd-secret",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{
|
|
||||||
"plugin.token": []byte("my-secret"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputParameters: map[string]apiextensionsv1.JSON{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
gotemplate: true,
|
|
||||||
content: []byte(`{"output": {
|
|
||||||
"parameters": [{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2": {
|
|
||||||
"key2_1": "val2_1",
|
|
||||||
"key2_2": {
|
|
||||||
"key2_2_1": "val2_2_1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"key3": 123
|
|
||||||
}]
|
|
||||||
}}`),
|
|
||||||
expected: []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2": map[string]interface{}{
|
|
||||||
"key2_1": "val2_1",
|
|
||||||
"key2_2": map[string]interface{}{
|
|
||||||
"key2_2_1": "val2_2_1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"key3": float64(123),
|
|
||||||
"generator": map[string]interface{}{
|
|
||||||
"input": argoprojiov1alpha1.PluginInput{
|
|
||||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedError: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "simple case with appended params",
|
|
||||||
configmap: &v1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "first-plugin-cm",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string]string{
|
|
||||||
"baseUrl": "http://127.0.0.1",
|
|
||||||
"token": "$plugin.token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: &v1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "argocd-secret",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{
|
|
||||||
"plugin.token": []byte("my-secret"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputParameters: map[string]apiextensionsv1.JSON{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
gotemplate: false,
|
|
||||||
content: []byte(`{"output": {"parameters": [{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2": {
|
|
||||||
"key2_1": "val2_1",
|
|
||||||
"key2_2": {
|
|
||||||
"key2_2_1": "val2_2_1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"key3": 123,
|
|
||||||
"pkey2": "valplugin"
|
|
||||||
}]}}`),
|
|
||||||
expected: []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2.key2_1": "val2_1",
|
|
||||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
|
||||||
"key3": "123",
|
|
||||||
"pkey2": "valplugin",
|
|
||||||
"generator": map[string]interface{}{
|
|
||||||
"input": argoprojiov1alpha1.PluginInput{
|
|
||||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedError: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no params",
|
|
||||||
configmap: &v1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "first-plugin-cm",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string]string{
|
|
||||||
"baseUrl": "http://127.0.0.1",
|
|
||||||
"token": "$plugin.token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: &v1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "argocd-secret",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{
|
|
||||||
"plugin.token": []byte("my-secret"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputParameters: argoprojiov1alpha1.PluginParameters{},
|
|
||||||
gotemplate: false,
|
|
||||||
content: []byte(`{"output": {
|
|
||||||
"parameters": [{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2": {
|
|
||||||
"key2_1": "val2_1",
|
|
||||||
"key2_2": {
|
|
||||||
"key2_2_1": "val2_2_1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"key3": 123
|
|
||||||
}]
|
|
||||||
}}`),
|
|
||||||
expected: []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2.key2_1": "val2_1",
|
|
||||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
|
||||||
"key3": "123",
|
|
||||||
"generator": map[string]interface{}{
|
|
||||||
"input": map[string]map[string]interface{}{
|
|
||||||
"parameters": {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedError: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty return",
|
|
||||||
configmap: &v1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "first-plugin-cm",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string]string{
|
|
||||||
"baseUrl": "http://127.0.0.1",
|
|
||||||
"token": "$plugin.token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: &v1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "argocd-secret",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{
|
|
||||||
"plugin.token": []byte("my-secret"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputParameters: map[string]apiextensionsv1.JSON{},
|
|
||||||
gotemplate: false,
|
|
||||||
content: []byte(`{"input": {"parameters": []}}`),
|
|
||||||
expected: []map[string]interface{}{},
|
|
||||||
expectedError: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "wrong return",
|
|
||||||
configmap: &v1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "first-plugin-cm",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string]string{
|
|
||||||
"baseUrl": "http://127.0.0.1",
|
|
||||||
"token": "$plugin.token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: &v1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "argocd-secret",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{
|
|
||||||
"plugin.token": []byte("my-secret"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputParameters: map[string]apiextensionsv1.JSON{},
|
|
||||||
gotemplate: false,
|
|
||||||
content: []byte(`wrong body ...`),
|
|
||||||
expected: []map[string]interface{}{},
|
|
||||||
expectedError: fmt.Errorf("error listing params: error get api 'set': invalid character 'w' looking for beginning of value: wrong body ..."),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "external secret",
|
|
||||||
configmap: &v1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "first-plugin-cm",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string]string{
|
|
||||||
"baseUrl": "http://127.0.0.1",
|
|
||||||
"token": "$plugin-secret:plugin.token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: &v1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "plugin-secret",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{
|
|
||||||
"plugin.token": []byte("my-secret"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputParameters: map[string]apiextensionsv1.JSON{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
gotemplate: false,
|
|
||||||
content: []byte(`{"output": {"parameters": [{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2": {
|
|
||||||
"key2_1": "val2_1",
|
|
||||||
"key2_2": {
|
|
||||||
"key2_2_1": "val2_2_1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"key3": 123,
|
|
||||||
"pkey2": "valplugin"
|
|
||||||
}]}}`),
|
|
||||||
expected: []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2.key2_1": "val2_1",
|
|
||||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
|
||||||
"key3": "123",
|
|
||||||
"pkey2": "valplugin",
|
|
||||||
"generator": map[string]interface{}{
|
|
||||||
"input": argoprojiov1alpha1.PluginInput{
|
|
||||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedError: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no secret",
|
|
||||||
configmap: &v1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "first-plugin-cm",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string]string{
|
|
||||||
"baseUrl": "http://127.0.0.1",
|
|
||||||
"token": "$plugin.token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: &v1.Secret{},
|
|
||||||
inputParameters: map[string]apiextensionsv1.JSON{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
gotemplate: false,
|
|
||||||
content: []byte(`{"output": {
|
|
||||||
"parameters": [{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2": {
|
|
||||||
"key2_1": "val2_1",
|
|
||||||
"key2_2": {
|
|
||||||
"key2_2_1": "val2_2_1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"key3": 123
|
|
||||||
}]
|
|
||||||
}}`),
|
|
||||||
expected: []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2.key2_1": "val2_1",
|
|
||||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
|
||||||
"key3": "123",
|
|
||||||
"generator": map[string]interface{}{
|
|
||||||
"input": argoprojiov1alpha1.PluginInput{
|
|
||||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedError: fmt.Errorf("error getting plugin from generator: error fetching Secret token: error fetching secret default/argocd-secret: secrets \"argocd-secret\" not found"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no configmap",
|
|
||||||
configmap: &v1.ConfigMap{},
|
|
||||||
secret: &v1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "argocd-secret",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{
|
|
||||||
"plugin.token": []byte("my-secret"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputParameters: map[string]apiextensionsv1.JSON{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
gotemplate: false,
|
|
||||||
content: []byte(`{"output": {
|
|
||||||
"parameters": [{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2": {
|
|
||||||
"key2_1": "val2_1",
|
|
||||||
"key2_2": {
|
|
||||||
"key2_2_1": "val2_2_1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"key3": 123
|
|
||||||
}]
|
|
||||||
}}`),
|
|
||||||
expected: []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2.key2_1": "val2_1",
|
|
||||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
|
||||||
"key3": "123",
|
|
||||||
"generator": map[string]interface{}{
|
|
||||||
"input": argoprojiov1alpha1.PluginInput{
|
|
||||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedError: fmt.Errorf("error getting plugin from generator: error fetching ConfigMap: configmaps \"\" not found"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no baseUrl",
|
|
||||||
configmap: &v1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "first-plugin-cm",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string]string{
|
|
||||||
"token": "$plugin.token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: &v1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "argocd-secret",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string][]byte{
|
|
||||||
"plugin.token": []byte("my-secret"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputParameters: map[string]apiextensionsv1.JSON{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
gotemplate: false,
|
|
||||||
content: []byte(`{"output": {
|
|
||||||
"parameters": [{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2": {
|
|
||||||
"key2_1": "val2_1",
|
|
||||||
"key2_2": {
|
|
||||||
"key2_2_1": "val2_2_1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"key3": 123
|
|
||||||
}]
|
|
||||||
}}`),
|
|
||||||
expected: []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2.key2_1": "val2_1",
|
|
||||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
|
||||||
"key3": "123",
|
|
||||||
"generator": map[string]interface{}{
|
|
||||||
"input": argoprojiov1alpha1.PluginInput{
|
|
||||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedError: fmt.Errorf("error getting plugin from generator: error fetching ConfigMap: baseUrl not found in ConfigMap"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no token",
|
|
||||||
configmap: &v1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "first-plugin-cm",
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
Data: map[string]string{
|
|
||||||
"baseUrl": "http://127.0.0.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
secret: &v1.Secret{},
|
|
||||||
inputParameters: map[string]apiextensionsv1.JSON{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
gotemplate: false,
|
|
||||||
content: []byte(`{"output": {
|
|
||||||
"parameters": [{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2": {
|
|
||||||
"key2_1": "val2_1",
|
|
||||||
"key2_2": {
|
|
||||||
"key2_2_1": "val2_2_1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"key3": 123
|
|
||||||
}]
|
|
||||||
}}`),
|
|
||||||
expected: []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2.key2_1": "val2_1",
|
|
||||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
|
||||||
"key3": "123",
|
|
||||||
"generator": map[string]interface{}{
|
|
||||||
"input": argoprojiov1alpha1.PluginInput{
|
|
||||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
|
||||||
"pkey1": {Raw: []byte(`"val1"`)},
|
|
||||||
"pkey2": {Raw: []byte(`"val2"`)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedError: fmt.Errorf("error getting plugin from generator: error fetching ConfigMap: token not found in ConfigMap"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
|
||||||
generatorConfig := argoprojiov1alpha1.ApplicationSetGenerator{
|
|
||||||
Plugin: &argoprojiov1alpha1.PluginGenerator{
|
|
||||||
ConfigMapRef: argoprojiov1alpha1.PluginConfigMapRef{Name: testCase.configmap.Name},
|
|
||||||
Input: argoprojiov1alpha1.PluginInput{
|
|
||||||
Parameters: testCase.inputParameters,
|
|
||||||
},
|
|
||||||
Values: testCase.values,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
authHeader := r.Header.Get("Authorization")
|
|
||||||
_, tokenKey := plugin.ParseSecretKey(testCase.configmap.Data["token"])
|
|
||||||
expectedToken := testCase.secret.Data[strings.ReplaceAll(tokenKey, "$", "")]
|
|
||||||
if authHeader != "Bearer "+string(expectedToken) {
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
_, err := w.Write(testCase.content)
|
|
||||||
if err != nil {
|
|
||||||
require.NoError(t, fmt.Errorf("Error Write %w", err))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
fakeServer := httptest.NewServer(handler)
|
|
||||||
|
|
||||||
defer fakeServer.Close()
|
|
||||||
|
|
||||||
if _, ok := testCase.configmap.Data["baseUrl"]; ok {
|
|
||||||
testCase.configmap.Data["baseUrl"] = fakeServer.URL
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeClient := kubefake.NewSimpleClientset(append([]runtime.Object{}, testCase.configmap, testCase.secret)...)
|
|
||||||
|
|
||||||
fakeClientWithCache := fake.NewClientBuilder().WithObjects([]client.Object{testCase.configmap, testCase.secret}...).Build()
|
|
||||||
|
|
||||||
pluginGenerator := NewPluginGenerator(fakeClientWithCache, ctx, fakeClient, "default")
|
|
||||||
|
|
||||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "set",
|
|
||||||
},
|
|
||||||
Spec: argoprojiov1alpha1.ApplicationSetSpec{
|
|
||||||
GoTemplate: testCase.gotemplate,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := pluginGenerator.GenerateParams(&generatorConfig, &applicationSetInfo, nil)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if testCase.expectedError != nil {
|
|
||||||
require.EqualError(t, err, testCase.expectedError.Error())
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
expectedJson, err := json.Marshal(testCase.expected)
|
|
||||||
require.NoError(t, err)
|
|
||||||
gotJson, err := json.Marshal(got)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.JSONEq(t, string(expectedJson), string(gotJson))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,12 +6,13 @@ 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"
|
||||||
|
|
||||||
|
"github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
|
||||||
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 +25,13 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPullRequestGenerator(client client.Client, scmConfig SCMConfig) Generator {
|
func NewPullRequestGenerator(client client.Client, auth SCMAuthProviders) Generator {
|
||||||
g := &PullRequestGenerator{
|
g := &PullRequestGenerator{
|
||||||
client: client,
|
client: client,
|
||||||
SCMConfig: scmConfig,
|
auth: auth,
|
||||||
}
|
}
|
||||||
g.selectServiceProviderFunc = g.selectServiceProvider
|
g.selectServiceProviderFunc = g.selectServiceProvider
|
||||||
return g
|
return g
|
||||||
@@ -50,7 +51,7 @@ func (g *PullRequestGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.A
|
|||||||
return &appSetGenerator.PullRequest.Template
|
return &appSetGenerator.PullRequest.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
|
func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
|
||||||
if appSetGenerator == nil {
|
if appSetGenerator == nil {
|
||||||
return nil, EmptyAppSetGeneratorError
|
return nil, EmptyAppSetGeneratorError
|
||||||
}
|
}
|
||||||
@@ -62,12 +63,12 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
svc, err := g.selectServiceProviderFunc(ctx, appSetGenerator.PullRequest, applicationSetInfo)
|
svc, err := g.selectServiceProviderFunc(ctx, appSetGenerator.PullRequest, applicationSetInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to select pull request service provider: %w", err)
|
return nil, fmt.Errorf("failed to select pull request service provider: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pulls, err := pullrequest.ListPullRequests(ctx, svc, appSetGenerator.PullRequest.Filters)
|
pulls, err := pull_request.ListPullRequests(ctx, svc, appSetGenerator.PullRequest.Filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error listing repos: %w", err)
|
return nil, fmt.Errorf("error listing repos: %v", err)
|
||||||
}
|
}
|
||||||
params := make([]map[string]interface{}, 0, len(pulls))
|
params := make([]map[string]interface{}, 0, len(pulls))
|
||||||
|
|
||||||
@@ -83,29 +84,18 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
|
|||||||
}
|
}
|
||||||
|
|
||||||
var shortSHALength int
|
var shortSHALength int
|
||||||
var shortSHALength7 int
|
|
||||||
for _, pull := range pulls {
|
for _, pull := range pulls {
|
||||||
shortSHALength = 8
|
shortSHALength = 8
|
||||||
if len(pull.HeadSHA) < 8 {
|
if len(pull.HeadSHA) < 8 {
|
||||||
shortSHALength = len(pull.HeadSHA)
|
shortSHALength = len(pull.HeadSHA)
|
||||||
}
|
}
|
||||||
|
|
||||||
shortSHALength7 = 7
|
|
||||||
if len(pull.HeadSHA) < 7 {
|
|
||||||
shortSHALength7 = len(pull.HeadSHA)
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
"head_sha": pull.HeadSHA,
|
||||||
"target_branch": pull.TargetBranch,
|
"head_short_sha": pull.HeadSHA[:shortSHALength],
|
||||||
"target_branch_slug": slug.Make(pull.TargetBranch),
|
|
||||||
"head_sha": pull.HeadSHA,
|
|
||||||
"head_short_sha": pull.HeadSHA[:shortSHALength],
|
|
||||||
"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.
|
||||||
@@ -119,109 +109,78 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
|
|||||||
|
|
||||||
// selectServiceProvider selects the provider to get pull requests from the configuration
|
// selectServiceProvider selects the provider to get pull requests from the configuration
|
||||||
func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, generatorConfig *argoprojiov1alpha1.PullRequestGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
|
func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, generatorConfig *argoprojiov1alpha1.PullRequestGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
|
||||||
if !g.enableSCMProviders {
|
|
||||||
return nil, ErrSCMProvidersDisabled
|
|
||||||
}
|
|
||||||
if err := ScmProviderAllowed(applicationSetInfo, generatorConfig, g.allowedSCMProviders); err != nil {
|
|
||||||
return nil, fmt.Errorf("scm provider not allowed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if generatorConfig.Github != nil {
|
if generatorConfig.Github != nil {
|
||||||
return g.github(ctx, generatorConfig.Github, applicationSetInfo)
|
return g.github(ctx, generatorConfig.Github, applicationSetInfo)
|
||||||
}
|
}
|
||||||
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: %v", 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)
|
||||||
}
|
}
|
||||||
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: %v", err)
|
||||||
}
|
}
|
||||||
return pullrequest.NewGiteaService(ctx, token, providerConfig.API, providerConfig.Owner, providerConfig.Repo, providerConfig.Insecure)
|
return pullrequest.NewGiteaService(ctx, token, providerConfig.API, providerConfig.Owner, providerConfig.Repo, providerConfig.Insecure)
|
||||||
}
|
}
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error fetching Secret Bearer token: %w", err)
|
return nil, fmt.Errorf("error fetching Secret token: %v", err)
|
||||||
}
|
}
|
||||||
return pullrequest.NewBitbucketServiceBearerToken(ctx, appToken, 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 if providerConfig.BasicAuth != nil {
|
|
||||||
password, err := utils.GetSecretRef(ctx, g.client, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
|
|
||||||
if err != nil {
|
|
||||||
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)
|
|
||||||
} 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 {
|
|
||||||
providerConfig := generatorConfig.Bitbucket
|
|
||||||
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.NewBitbucketCloudServiceBearerToken(providerConfig.API, appToken, providerConfig.Owner, providerConfig.Repo)
|
|
||||||
} else if providerConfig.BasicAuth != nil {
|
|
||||||
password, err := utils.GetSecretRef(ctx, g.client, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error fetching Secret token: %w", err)
|
|
||||||
}
|
|
||||||
return pullrequest.NewBitbucketCloudServiceBasicAuth(providerConfig.API, providerConfig.BasicAuth.Username, password, providerConfig.Owner, providerConfig.Repo)
|
|
||||||
} else {
|
|
||||||
return pullrequest.NewBitbucketCloudServiceNoAuth(providerConfig.API, providerConfig.Owner, providerConfig.Repo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if generatorConfig.AzureDevOps != nil {
|
|
||||||
providerConfig := generatorConfig.AzureDevOps
|
|
||||||
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error fetching Secret token: %w", err)
|
|
||||||
}
|
|
||||||
return pullrequest.NewAzureDevOpsService(ctx, token, providerConfig.API, providerConfig.Organization, providerConfig.Project, providerConfig.Repo, providerConfig.Labels)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("no Pull Request provider implementation configured")
|
return nil, fmt.Errorf("no Pull Request provider implementation configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alpha1.PullRequestGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
|
func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alpha1.PullRequestGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
|
||||||
// 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: %v", err)
|
||||||
}
|
}
|
||||||
return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
|
return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
|
||||||
}
|
}
|
||||||
|
|
||||||
// always default to token, even if not set (public access)
|
// always default to token, even if not set (public access)
|
||||||
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: %v", 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: %v", 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"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"
|
||||||
@@ -26,13 +27,10 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
|
|||||||
return pullrequest.NewFakeService(
|
return pullrequest.NewFakeService(
|
||||||
ctx,
|
ctx,
|
||||||
[]*pullrequest.PullRequest{
|
[]*pullrequest.PullRequest{
|
||||||
{
|
&pullrequest.PullRequest{
|
||||||
Number: 1,
|
Number: 1,
|
||||||
Title: "title1",
|
Branch: "branch1",
|
||||||
Branch: "branch1",
|
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||||
TargetBranch: "master",
|
|
||||||
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
|
||||||
Author: "testName",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
@@ -40,16 +38,11 @@ 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",
|
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||||
"target_branch": "master",
|
"head_short_sha": "089d92cb",
|
||||||
"target_branch_slug": "master",
|
|
||||||
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
|
||||||
"head_short_sha": "089d92cb",
|
|
||||||
"head_short_sha_7": "089d92c",
|
|
||||||
"author": "testName",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
@@ -59,13 +52,10 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
|
|||||||
return pullrequest.NewFakeService(
|
return pullrequest.NewFakeService(
|
||||||
ctx,
|
ctx,
|
||||||
[]*pullrequest.PullRequest{
|
[]*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",
|
HeadSHA: "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
|
||||||
TargetBranch: "feat/anotherreally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
|
|
||||||
HeadSHA: "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
|
|
||||||
Author: "testName",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
@@ -73,16 +63,11 @@ 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",
|
"head_sha": "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
|
||||||
"target_branch": "feat/anotherreally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
|
"head_short_sha": "9b34ff5b",
|
||||||
"target_branch_slug": "feat-anotherreally-long-pull-request-name-to-test",
|
|
||||||
"head_sha": "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
|
|
||||||
"head_short_sha": "9b34ff5b",
|
|
||||||
"head_short_sha_7": "9b34ff5",
|
|
||||||
"author": "testName",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
@@ -92,13 +77,10 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
|
|||||||
return pullrequest.NewFakeService(
|
return pullrequest.NewFakeService(
|
||||||
ctx,
|
ctx,
|
||||||
[]*pullrequest.PullRequest{
|
[]*pullrequest.PullRequest{
|
||||||
{
|
&pullrequest.PullRequest{
|
||||||
Number: 1,
|
Number: 1,
|
||||||
Title: "title1",
|
Branch: "a-very-short-sha",
|
||||||
Branch: "a-very-short-sha",
|
HeadSHA: "abcd",
|
||||||
TargetBranch: "master",
|
|
||||||
HeadSHA: "abcd",
|
|
||||||
Author: "testName",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
@@ -106,16 +88,11 @@ 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",
|
"head_sha": "abcd",
|
||||||
"target_branch": "master",
|
"head_short_sha": "abcd",
|
||||||
"target_branch_slug": "master",
|
|
||||||
"head_sha": "abcd",
|
|
||||||
"head_short_sha": "abcd",
|
|
||||||
"head_short_sha_7": "abcd",
|
|
||||||
"author": "testName",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
@@ -136,14 +113,11 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
|
|||||||
return pullrequest.NewFakeService(
|
return pullrequest.NewFakeService(
|
||||||
ctx,
|
ctx,
|
||||||
[]*pullrequest.PullRequest{
|
[]*pullrequest.PullRequest{
|
||||||
{
|
&pullrequest.PullRequest{
|
||||||
Number: 1,
|
Number: 1,
|
||||||
Title: "title1",
|
Branch: "branch1",
|
||||||
Branch: "branch1",
|
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||||
TargetBranch: "master",
|
Labels: []string{"preview"},
|
||||||
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
|
||||||
Labels: []string{"preview"},
|
|
||||||
Author: "testName",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
@@ -151,17 +125,12 @@ 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",
|
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||||
"target_branch": "master",
|
"head_short_sha": "089d92cb",
|
||||||
"target_branch_slug": "master",
|
"labels": []string{"preview"},
|
||||||
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
|
||||||
"head_short_sha": "089d92cb",
|
|
||||||
"head_short_sha_7": "089d92c",
|
|
||||||
"labels": []string{"preview"},
|
|
||||||
"author": "testName",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
@@ -177,14 +146,11 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
|
|||||||
return pullrequest.NewFakeService(
|
return pullrequest.NewFakeService(
|
||||||
ctx,
|
ctx,
|
||||||
[]*pullrequest.PullRequest{
|
[]*pullrequest.PullRequest{
|
||||||
{
|
&pullrequest.PullRequest{
|
||||||
Number: 1,
|
Number: 1,
|
||||||
Title: "title1",
|
Branch: "branch1",
|
||||||
Branch: "branch1",
|
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||||
TargetBranch: "master",
|
Labels: []string{"preview"},
|
||||||
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
|
||||||
Labels: []string{"preview"},
|
|
||||||
Author: "testName",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
@@ -192,16 +158,11 @@ 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",
|
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||||
"target_branch": "master",
|
"head_short_sha": "089d92cb",
|
||||||
"target_branch_slug": "master",
|
|
||||||
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
|
||||||
"head_short_sha": "089d92cb",
|
|
||||||
"head_short_sha_7": "089d92c",
|
|
||||||
"author": "testName",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
@@ -222,107 +183,73 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
|
|||||||
PullRequest: &argoprojiov1alpha1.PullRequestGenerator{},
|
PullRequest: &argoprojiov1alpha1.PullRequestGenerator{},
|
||||||
}
|
}
|
||||||
|
|
||||||
got, gotErr := gen.GenerateParams(&generatorConfig, &c.applicationSet, nil)
|
got, gotErr := gen.GenerateParams(&generatorConfig, &c.applicationSet)
|
||||||
if c.expectedErr != nil {
|
assert.Equal(t, c.expectedErr, gotErr)
|
||||||
assert.Equal(t, c.expectedErr.Error(), gotErr.Error())
|
|
||||||
} else {
|
|
||||||
require.NoError(t, gotErr)
|
|
||||||
}
|
|
||||||
assert.ElementsMatch(t, c.expected, got)
|
assert.ElementsMatch(t, c.expected, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAllowedSCMProviderPullRequest(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 {
|
cases := []struct {
|
||||||
name string
|
name, namespace, token string
|
||||||
providerConfig *argoprojiov1alpha1.PullRequestGenerator
|
ref *argoprojiov1alpha1.SecretRef
|
||||||
|
hasError bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Error Github",
|
name: "valid ref",
|
||||||
providerConfig: &argoprojiov1alpha1.PullRequestGenerator{
|
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
|
||||||
Github: &argoprojiov1alpha1.PullRequestGeneratorGithub{
|
namespace: "test",
|
||||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
token: "secret",
|
||||||
},
|
hasError: false,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Error Gitlab",
|
name: "nil ref",
|
||||||
providerConfig: &argoprojiov1alpha1.PullRequestGenerator{
|
ref: nil,
|
||||||
GitLab: &argoprojiov1alpha1.PullRequestGeneratorGitLab{
|
namespace: "test",
|
||||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
token: "",
|
||||||
},
|
hasError: false,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Error Gitea",
|
name: "wrong name",
|
||||||
providerConfig: &argoprojiov1alpha1.PullRequestGenerator{
|
ref: &argoprojiov1alpha1.SecretRef{SecretName: "other", Key: "my-token"},
|
||||||
Gitea: &argoprojiov1alpha1.PullRequestGeneratorGitea{
|
namespace: "test",
|
||||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
token: "",
|
||||||
},
|
hasError: true,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Error Bitbucket",
|
name: "wrong key",
|
||||||
providerConfig: &argoprojiov1alpha1.PullRequestGenerator{
|
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "other-token"},
|
||||||
BitbucketServer: &argoprojiov1alpha1.PullRequestGeneratorBitbucketServer{
|
namespace: "test",
|
||||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
token: "",
|
||||||
},
|
hasError: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "wrong namespace",
|
||||||
|
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
|
||||||
|
namespace: "other",
|
||||||
|
token: "",
|
||||||
|
hasError: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range cases {
|
for _, c := range cases {
|
||||||
testCaseCopy := testCase
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
token, err := gen.getSecretRef(ctx, c.ref, c.namespace)
|
||||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
if c.hasError {
|
||||||
t.Parallel()
|
assert.NotNil(t, err)
|
||||||
|
} else {
|
||||||
pullRequestGenerator := NewPullRequestGenerator(nil, NewSCMConfig("", []string{
|
assert.Nil(t, err)
|
||||||
"github.myorg.com",
|
|
||||||
"gitlab.myorg.com",
|
|
||||||
"gitea.myorg.com",
|
|
||||||
"bitbucket.myorg.com",
|
|
||||||
"azuredevops.myorg.com",
|
|
||||||
}, true, nil, true))
|
|
||||||
|
|
||||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "set",
|
|
||||||
},
|
|
||||||
Spec: argoprojiov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
|
|
||||||
PullRequest: testCaseCopy.providerConfig,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
assert.Equal(t, c.token, token)
|
||||||
_, err := pullRequestGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
|
|
||||||
|
|
||||||
require.Error(t, err, "Must return an error")
|
|
||||||
var expectedError ErrDisallowedSCMProvider
|
|
||||||
assert.ErrorAs(t, err, &expectedError)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSCMProviderDisabled_PRGenerator(t *testing.T) {
|
|
||||||
generator := NewPullRequestGenerator(nil, NewSCMConfig("", []string{}, false, nil, true))
|
|
||||||
|
|
||||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "set",
|
|
||||||
},
|
|
||||||
Spec: argoprojiov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
|
|
||||||
PullRequest: &argoprojiov1alpha1.PullRequestGenerator{
|
|
||||||
Github: &argoprojiov1alpha1.PullRequestGeneratorGithub{
|
|
||||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := generator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
|
|
||||||
assert.ErrorIs(t, err, ErrSCMProvidersDisabled)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,19 +2,16 @@ package generators
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"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"
|
|
||||||
|
|
||||||
"github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth"
|
"github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth"
|
||||||
"github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider"
|
"github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider"
|
||||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
||||||
"github.com/argoproj/argo-cd/v2/common"
|
|
||||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,38 +25,23 @@ 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
|
|
||||||
allowedSCMProviders []string
|
|
||||||
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
|
||||||
scmRootCAPath: scmRootCAPath,
|
|
||||||
allowedSCMProviders: allowedSCMProviders,
|
|
||||||
enableSCMProviders: enableSCMProviders,
|
|
||||||
GitHubApps: gitHubApps,
|
|
||||||
tokenRefStrictMode: tokenRefStrictMode,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSCMProviderGenerator(client client.Client, scmConfig SCMConfig) Generator {
|
func NewSCMProviderGenerator(client client.Client, providers SCMAuthProviders) Generator {
|
||||||
return &SCMProviderGenerator{
|
return &SCMProviderGenerator{
|
||||||
client: client,
|
client: client,
|
||||||
SCMConfig: scmConfig,
|
SCMAuthProviders: providers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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,
|
|
||||||
}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *SCMProviderGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
|
func (g *SCMProviderGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
|
||||||
@@ -76,47 +58,7 @@ func (g *SCMProviderGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.A
|
|||||||
return &appSetGenerator.SCMProvider.Template
|
return &appSetGenerator.SCMProvider.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrSCMProvidersDisabled = errors.New("scm providers are disabled")
|
func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
|
||||||
|
|
||||||
type ErrDisallowedSCMProvider struct {
|
|
||||||
Provider string
|
|
||||||
Allowed []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewErrDisallowedSCMProvider(provider string, allowed []string) ErrDisallowedSCMProvider {
|
|
||||||
return ErrDisallowedSCMProvider{
|
|
||||||
Provider: provider,
|
|
||||||
Allowed: allowed,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e ErrDisallowedSCMProvider) Error() string {
|
|
||||||
return fmt.Sprintf("scm provider %q not allowed, must use one of the following: %s", e.Provider, strings.Join(e.Allowed, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
func ScmProviderAllowed(applicationSetInfo *argoprojiov1alpha1.ApplicationSet, generator SCMGeneratorWithCustomApiUrl, allowedScmProviders []string) error {
|
|
||||||
url := generator.CustomApiUrl()
|
|
||||||
|
|
||||||
if url == "" || len(allowedScmProviders) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, allowedScmProvider := range allowedScmProviders {
|
|
||||||
if url == allowedScmProvider {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
common.SecurityField: common.SecurityMedium,
|
|
||||||
"applicationset": applicationSetInfo.Name,
|
|
||||||
"appSetNamespace": applicationSetInfo.Namespace,
|
|
||||||
}).Debugf("attempted to use disallowed SCM %q, must use one of the following: %s", url, strings.Join(allowedScmProviders, ", "))
|
|
||||||
|
|
||||||
return NewErrDisallowedSCMProvider(url, allowedScmProviders)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
|
|
||||||
if appSetGenerator == nil {
|
if appSetGenerator == nil {
|
||||||
return nil, EmptyAppSetGeneratorError
|
return nil, EmptyAppSetGeneratorError
|
||||||
}
|
}
|
||||||
@@ -125,18 +67,10 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
|
|||||||
return nil, EmptyAppSetGeneratorError
|
return nil, EmptyAppSetGeneratorError
|
||||||
}
|
}
|
||||||
|
|
||||||
if !g.enableSCMProviders {
|
ctx := context.Background()
|
||||||
return nil, ErrSCMProvidersDisabled
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the SCM provider helper.
|
// Create the SCM provider helper.
|
||||||
providerConfig := appSetGenerator.SCMProvider
|
providerConfig := appSetGenerator.SCMProvider
|
||||||
|
|
||||||
if err := ScmProviderAllowed(applicationSetInfo, providerConfig, g.allowedSCMProviders); err != nil {
|
|
||||||
return nil, fmt.Errorf("scm provider not allowed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
var provider scm_provider.SCMProviderService
|
var provider scm_provider.SCMProviderService
|
||||||
if g.overrideProvider != nil {
|
if g.overrideProvider != nil {
|
||||||
provider = g.overrideProvider
|
provider = g.overrideProvider
|
||||||
@@ -147,83 +81,55 @@ 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: %v", 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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error initializing Gitlab service: %w", err)
|
return nil, fmt.Errorf("error initializing Gitlab service: %v", 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: %v", err)
|
||||||
}
|
}
|
||||||
provider, err = scm_provider.NewGiteaProvider(ctx, providerConfig.Gitea.Owner, token, providerConfig.Gitea.API, providerConfig.Gitea.AllBranches, providerConfig.Gitea.Insecure)
|
provider, err = scm_provider.NewGiteaProvider(ctx, providerConfig.Gitea.Owner, token, providerConfig.Gitea.API, providerConfig.Gitea.AllBranches, providerConfig.Gitea.Insecure)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error initializing Gitea service: %w", err)
|
return nil, fmt.Errorf("error initializing Gitea service: %v", err)
|
||||||
}
|
}
|
||||||
} 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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error fetching Secret Bearer token: %w", err)
|
return nil, fmt.Errorf("error fetching Secret token: %v", err)
|
||||||
}
|
}
|
||||||
provider, scmError = scm_provider.NewBitbucketServerProviderBearerToken(ctx, appToken, 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 if providerConfig.BasicAuth != nil {
|
|
||||||
password, err := utils.GetSecretRef(ctx, g.client, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
|
|
||||||
if err != nil {
|
|
||||||
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)
|
|
||||||
} 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: %v", 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: %v", err)
|
||||||
}
|
}
|
||||||
provider, err = scm_provider.NewAzureDevOpsProvider(ctx, token, providerConfig.AzureDevOps.Organization, providerConfig.AzureDevOps.API, providerConfig.AzureDevOps.TeamProject, providerConfig.AzureDevOps.AllBranches)
|
provider, err = scm_provider.NewAzureDevOpsProvider(ctx, token, providerConfig.AzureDevOps.Organization, providerConfig.AzureDevOps.API, providerConfig.AzureDevOps.TeamProject, providerConfig.AzureDevOps.AllBranches)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error initializing Azure Devops service: %w", err)
|
return nil, fmt.Errorf("error initializing Azure Devops service: %v", 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: %v", err)
|
||||||
}
|
}
|
||||||
provider, err = scm_provider.NewBitBucketCloudProvider(ctx, providerConfig.Bitbucket.Owner, providerConfig.Bitbucket.User, appPassword, providerConfig.Bitbucket.AllBranches)
|
provider, err = scm_provider.NewBitBucketCloudProvider(ctx, providerConfig.Bitbucket.Owner, providerConfig.Bitbucket.User, appPassword, providerConfig.Bitbucket.AllBranches)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error initializing Bitbucket cloud service: %w", err)
|
return nil, fmt.Errorf("error initializing Bitbucket cloud service: %v", err)
|
||||||
}
|
|
||||||
} else if providerConfig.AWSCodeCommit != nil {
|
|
||||||
var awsErr error
|
|
||||||
provider, awsErr = scm_provider.NewAWSCodeCommitProvider(ctx, providerConfig.AWSCodeCommit.TagFilters, providerConfig.AWSCodeCommit.Role, providerConfig.AWSCodeCommit.Region, providerConfig.AWSCodeCommit.AllBranches)
|
|
||||||
if awsErr != nil {
|
|
||||||
return nil, fmt.Errorf("error initializing AWS codecommit service: %w", awsErr)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("no SCM provider implementation configured")
|
return nil, fmt.Errorf("no SCM provider implementation configured")
|
||||||
@@ -232,49 +138,58 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
|
|||||||
// Find all the available repos.
|
// Find all the available repos.
|
||||||
repos, err := scm_provider.ListRepos(ctx, provider, providerConfig.Filters, providerConfig.CloneProtocol)
|
repos, err := scm_provider.ListRepos(ctx, provider, providerConfig.Filters, providerConfig.CloneProtocol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error listing repos: %w", err)
|
return nil, fmt.Errorf("error listing repos: %v", err)
|
||||||
}
|
}
|
||||||
paramsArray := make([]map[string]interface{}, 0, len(repos))
|
params := make([]map[string]interface{}, 0, len(repos))
|
||||||
var shortSHALength int
|
var shortSHALength int
|
||||||
var shortSHALength7 int
|
|
||||||
for _, repo := range repos {
|
for _, repo := range repos {
|
||||||
shortSHALength = 8
|
shortSHALength = 8
|
||||||
if len(repo.SHA) < 8 {
|
if len(repo.SHA) < 8 {
|
||||||
shortSHALength = len(repo.SHA)
|
shortSHALength = len(repo.SHA)
|
||||||
}
|
}
|
||||||
|
|
||||||
shortSHALength7 = 7
|
params = append(params, map[string]interface{}{
|
||||||
if len(repo.SHA) < 7 {
|
|
||||||
shortSHALength7 = len(repo.SHA)
|
|
||||||
}
|
|
||||||
|
|
||||||
params := map[string]interface{}{
|
|
||||||
"organization": repo.Organization,
|
"organization": repo.Organization,
|
||||||
"repository": repo.Repository,
|
"repository": repo.Repository,
|
||||||
"url": repo.URL,
|
"url": repo.URL,
|
||||||
"branch": repo.Branch,
|
"branch": repo.Branch,
|
||||||
"sha": repo.SHA,
|
"sha": repo.SHA,
|
||||||
"short_sha": repo.SHA[:shortSHALength],
|
"short_sha": repo.SHA[:shortSHALength],
|
||||||
"short_sha_7": repo.SHA[:shortSHALength7],
|
|
||||||
"labels": strings.Join(repo.Labels, ","),
|
"labels": strings.Join(repo.Labels, ","),
|
||||||
"branchNormalized": utils.SanitizeName(repo.Branch),
|
"branchNormalized": utils.SanitizeName(repo.Branch),
|
||||||
}
|
})
|
||||||
|
|
||||||
err := appendTemplatedValues(appSetGenerator.SCMProvider.Values, params, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to append templated values: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
paramsArray = append(paramsArray, params)
|
|
||||||
}
|
}
|
||||||
return paramsArray, nil
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *SCMProviderGenerator) getSecretRef(ctx context.Context, ref *argoprojiov1alpha1.SecretRef, namespace string) (string, error) {
|
||||||
|
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: %v", 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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error fetching Github app secret: %w", err)
|
return nil, fmt.Errorf("error fetching Github app secret: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return scm_provider.NewGithubAppProviderFor(
|
return scm_provider.NewGithubAppProviderFor(
|
||||||
@@ -285,9 +200,9 @@ 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: %v", err)
|
||||||
}
|
}
|
||||||
return scm_provider.NewGithubProvider(ctx, github.Organization, token, github.API, github.AllBranches)
|
return scm_provider.NewGithubProvider(ctx, github.Organization, token, github.API, github.AllBranches)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,241 +1,117 @@
|
|||||||
package generators
|
package generators
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"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 TestSCMProviderGenerateParams(t *testing.T) {
|
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 {
|
cases := []struct {
|
||||||
name string
|
name, namespace, token string
|
||||||
repos []*scm_provider.Repository
|
ref *argoprojiov1alpha1.SecretRef
|
||||||
values map[string]string
|
hasError bool
|
||||||
expected []map[string]interface{}
|
|
||||||
expectedError error
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Multiple repos with labels",
|
name: "valid ref",
|
||||||
repos: []*scm_provider.Repository{
|
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
|
||||||
{
|
namespace: "test",
|
||||||
Organization: "myorg",
|
token: "secret",
|
||||||
Repository: "repo1",
|
hasError: false,
|
||||||
URL: "git@github.com:myorg/repo1.git",
|
|
||||||
Branch: "main",
|
|
||||||
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
|
|
||||||
Labels: []string{"prod", "staging"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Organization: "myorg",
|
|
||||||
Repository: "repo2",
|
|
||||||
URL: "git@github.com:myorg/repo2.git",
|
|
||||||
Branch: "main",
|
|
||||||
SHA: "59d0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"organization": "myorg",
|
|
||||||
"repository": "repo1",
|
|
||||||
"url": "git@github.com:myorg/repo1.git",
|
|
||||||
"branch": "main",
|
|
||||||
"branchNormalized": "main",
|
|
||||||
"sha": "0bc57212c3cbbec69d20b34c507284bd300def5b",
|
|
||||||
"short_sha": "0bc57212",
|
|
||||||
"short_sha_7": "0bc5721",
|
|
||||||
"labels": "prod,staging",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"organization": "myorg",
|
|
||||||
"repository": "repo2",
|
|
||||||
"url": "git@github.com:myorg/repo2.git",
|
|
||||||
"branch": "main",
|
|
||||||
"branchNormalized": "main",
|
|
||||||
"sha": "59d0",
|
|
||||||
"short_sha": "59d0",
|
|
||||||
"short_sha_7": "59d0",
|
|
||||||
"labels": "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Value interpolation",
|
name: "nil ref",
|
||||||
repos: []*scm_provider.Repository{
|
ref: nil,
|
||||||
{
|
namespace: "test",
|
||||||
Organization: "myorg",
|
token: "",
|
||||||
Repository: "repo3",
|
hasError: false,
|
||||||
URL: "git@github.com:myorg/repo3.git",
|
},
|
||||||
Branch: "main",
|
{
|
||||||
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
|
name: "wrong name",
|
||||||
Labels: []string{"prod", "staging"},
|
ref: &argoprojiov1alpha1.SecretRef{SecretName: "other", Key: "my-token"},
|
||||||
},
|
namespace: "test",
|
||||||
},
|
token: "",
|
||||||
values: map[string]string{
|
hasError: true,
|
||||||
"foo": "bar",
|
},
|
||||||
"should_i_force_push_to": "{{ branch }}?",
|
{
|
||||||
},
|
name: "wrong key",
|
||||||
expected: []map[string]interface{}{
|
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "other-token"},
|
||||||
{
|
namespace: "test",
|
||||||
"organization": "myorg",
|
token: "",
|
||||||
"repository": "repo3",
|
hasError: true,
|
||||||
"url": "git@github.com:myorg/repo3.git",
|
},
|
||||||
"branch": "main",
|
{
|
||||||
"branchNormalized": "main",
|
name: "wrong namespace",
|
||||||
"sha": "0bc57212c3cbbec69d20b34c507284bd300def5b",
|
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
|
||||||
"short_sha": "0bc57212",
|
namespace: "other",
|
||||||
"short_sha_7": "0bc5721",
|
token: "",
|
||||||
"labels": "prod,staging",
|
hasError: true,
|
||||||
"values.foo": "bar",
|
|
||||||
"values.should_i_force_push_to": "main?",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range cases {
|
for _, c := range cases {
|
||||||
testCaseCopy := testCase
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
token, err := gen.getSecretRef(ctx, c.ref, c.namespace)
|
||||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
if c.hasError {
|
||||||
t.Parallel()
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
mockProvider := &scm_provider.MockProvider{
|
|
||||||
Repos: testCaseCopy.repos,
|
|
||||||
}
|
|
||||||
scmGenerator := &SCMProviderGenerator{overrideProvider: mockProvider, SCMConfig: SCMConfig{enableSCMProviders: true}}
|
|
||||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "set",
|
|
||||||
},
|
|
||||||
Spec: argoprojiov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
|
|
||||||
SCMProvider: &argoprojiov1alpha1.SCMProviderGenerator{
|
|
||||||
Values: testCaseCopy.values,
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
|
|
||||||
|
|
||||||
if testCaseCopy.expectedError != nil {
|
|
||||||
assert.EqualError(t, err, testCaseCopy.expectedError.Error())
|
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, testCaseCopy.expected, got)
|
|
||||||
}
|
}
|
||||||
|
assert.Equal(t, c.token, token)
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAllowedSCMProvider(t *testing.T) {
|
func TestSCMProviderGenerateParams(t *testing.T) {
|
||||||
cases := []struct {
|
mockProvider := &scm_provider.MockProvider{
|
||||||
name string
|
Repos: []*scm_provider.Repository{
|
||||||
providerConfig *argoprojiov1alpha1.SCMProviderGenerator
|
{
|
||||||
}{
|
Organization: "myorg",
|
||||||
{
|
Repository: "repo1",
|
||||||
name: "Error Github",
|
URL: "git@github.com:myorg/repo1.git",
|
||||||
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
|
Branch: "main",
|
||||||
Github: &argoprojiov1alpha1.SCMProviderGeneratorGithub{
|
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
|
||||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
Labels: []string{"prod", "staging"},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
Organization: "myorg",
|
||||||
name: "Error Gitlab",
|
Repository: "repo2",
|
||||||
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
|
URL: "git@github.com:myorg/repo2.git",
|
||||||
Gitlab: &argoprojiov1alpha1.SCMProviderGeneratorGitlab{
|
Branch: "main",
|
||||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
SHA: "59d0",
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Error Gitea",
|
|
||||||
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
|
|
||||||
Gitea: &argoprojiov1alpha1.SCMProviderGeneratorGitea{
|
|
||||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Error Bitbucket",
|
|
||||||
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
|
|
||||||
BitbucketServer: &argoprojiov1alpha1.SCMProviderGeneratorBitbucketServer{
|
|
||||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Error AzureDevops",
|
|
||||||
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
|
|
||||||
AzureDevOps: &argoprojiov1alpha1.SCMProviderGeneratorAzureDevOps{
|
|
||||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
gen := &SCMProviderGenerator{overrideProvider: mockProvider}
|
||||||
for _, testCase := range cases {
|
params, err := gen.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
|
||||||
testCaseCopy := testCase
|
SCMProvider: &argoprojiov1alpha1.SCMProviderGenerator{},
|
||||||
|
}, nil)
|
||||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
assert.Nil(t, err)
|
||||||
t.Parallel()
|
assert.Len(t, params, 2)
|
||||||
|
assert.Equal(t, "myorg", params[0]["organization"])
|
||||||
scmGenerator := &SCMProviderGenerator{
|
assert.Equal(t, "repo1", params[0]["repository"])
|
||||||
SCMConfig: SCMConfig{
|
assert.Equal(t, "git@github.com:myorg/repo1.git", params[0]["url"])
|
||||||
allowedSCMProviders: []string{
|
assert.Equal(t, "main", params[0]["branch"])
|
||||||
"github.myorg.com",
|
assert.Equal(t, "0bc57212c3cbbec69d20b34c507284bd300def5b", params[0]["sha"])
|
||||||
"gitlab.myorg.com",
|
assert.Equal(t, "0bc57212", params[0]["short_sha"])
|
||||||
"gitea.myorg.com",
|
assert.Equal(t, "59d0", params[1]["short_sha"])
|
||||||
"bitbucket.myorg.com",
|
assert.Equal(t, "prod,staging", params[0]["labels"])
|
||||||
"azuredevops.myorg.com",
|
assert.Equal(t, "repo2", params[1]["repository"])
|
||||||
},
|
|
||||||
enableSCMProviders: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "set",
|
|
||||||
},
|
|
||||||
Spec: argoprojiov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
|
|
||||||
SCMProvider: testCaseCopy.providerConfig,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
|
|
||||||
|
|
||||||
require.Error(t, err, "Must return an error")
|
|
||||||
var expectedError ErrDisallowedSCMProvider
|
|
||||||
assert.ErrorAs(t, err, &expectedError)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSCMProviderDisabled_SCMGenerator(t *testing.T) {
|
|
||||||
generator := &SCMProviderGenerator{SCMConfig: SCMConfig{enableSCMProviders: false}}
|
|
||||||
|
|
||||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "set",
|
|
||||||
},
|
|
||||||
Spec: argoprojiov1alpha1.ApplicationSetSpec{
|
|
||||||
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
|
|
||||||
SCMProvider: &argoprojiov1alpha1.SCMProviderGenerator{
|
|
||||||
Github: &argoprojiov1alpha1.SCMProviderGeneratorGithub{
|
|
||||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := generator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
|
|
||||||
assert.ErrorIs(t, err, ErrSCMProvidersDisabled)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package generators
|
|
||||||
|
|
||||||
type SCMGeneratorWithCustomApiUrl interface {
|
|
||||||
CustomApiUrl() string
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user