mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-03-06 08:28:50 +01:00
Compare commits
310 Commits
vendor-res
...
release-2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0554262ccb | ||
|
|
3397b935b2 | ||
|
|
1503fd9480 | ||
|
|
003d224c80 | ||
|
|
e19d6dfa1f | ||
|
|
5bb14a8873 | ||
|
|
3ab4b2b42c | ||
|
|
d7c2dd58e4 | ||
|
|
daaf88a2c6 | ||
|
|
74600fc7a1 | ||
|
|
df7e6df64c | ||
|
|
2fa12c8c99 | ||
|
|
f627b62f29 | ||
|
|
1680a41fa6 | ||
|
|
21988d857a | ||
|
|
791231490c | ||
|
|
f37a682fb1 | ||
|
|
305fcb556b | ||
|
|
bea852b1ea | ||
|
|
fb9b608da1 | ||
|
|
18f5bfab92 | ||
|
|
ad335936e7 | ||
|
|
b359505688 | ||
|
|
a5d6602385 | ||
|
|
9f09e08548 | ||
|
|
b43bb659cf | ||
|
|
d62b03781c | ||
|
|
a6d2438d13 | ||
|
|
0ffa7d4bde | ||
|
|
5e5b72da05 | ||
|
|
d296b41517 | ||
|
|
6f83c61dab | ||
|
|
b56ef0584c | ||
|
|
8b0b947144 | ||
|
|
431dc6cb40 | ||
|
|
801e19518e | ||
|
|
deaaf9aafd | ||
|
|
5640b727a4 | ||
|
|
e166cfaf7e | ||
|
|
e1184b4f3d | ||
|
|
7aea7281ca | ||
|
|
389a36bff8 | ||
|
|
930b4c9f9a | ||
|
|
9557dd6098 | ||
|
|
62ab3a4a63 | ||
|
|
da001b2a95 | ||
|
|
c06f954730 | ||
|
|
c5dfd754e4 | ||
|
|
f8e5340f7d | ||
|
|
20a5873fd8 | ||
|
|
e75cad1c37 | ||
|
|
63b9319453 | ||
|
|
e2d3a9ad68 | ||
|
|
0882565801 | ||
|
|
e6e4d584ed | ||
|
|
89d4622ffa | ||
|
|
3b866db875 | ||
|
|
75a65e97cf | ||
|
|
930017a3f1 | ||
|
|
77fdcb7699 | ||
|
|
3edc147e74 | ||
|
|
034e6cc92a | ||
|
|
ee174fa349 | ||
|
|
36c987b3c2 | ||
|
|
7d45aa99ba | ||
|
|
6e19199e10 | ||
|
|
84fbc93016 | ||
|
|
f80ae69050 | ||
|
|
06b9a25f6d | ||
|
|
64a7bb8f02 | ||
|
|
bc1908cdea | ||
|
|
4bacb3a381 | ||
|
|
9e06a83c2f | ||
|
|
4a02cf1639 | ||
|
|
7a5cf79984 | ||
|
|
798642dc19 | ||
|
|
aa60207496 | ||
|
|
5611eea361 | ||
|
|
4376093af0 | ||
|
|
435691827a | ||
|
|
3bcdec30f3 | ||
|
|
ccd7f76768 | ||
|
|
c2cb6691dd | ||
|
|
eb06207959 | ||
|
|
23c950728a | ||
|
|
1c0a505140 | ||
|
|
b1986bb6a5 | ||
|
|
36a134fa4a | ||
|
|
b4df63495a | ||
|
|
1a5bcf858a | ||
|
|
c9ca3ff5f6 | ||
|
|
64a09a1fb6 | ||
|
|
bb24b5c38a | ||
|
|
1125042e5b | ||
|
|
471e386ff0 | ||
|
|
ae3f5402ab | ||
|
|
1dfb61ec60 | ||
|
|
4cb8de4501 | ||
|
|
403ec2786b | ||
|
|
9075a56650 | ||
|
|
5ccaafc504 | ||
|
|
58bb6adc79 | ||
|
|
7aa54a5e17 | ||
|
|
6ade5f2bad | ||
|
|
9cd67b16f3 | ||
|
|
33d756e8d9 | ||
|
|
48edd4d998 | ||
|
|
3e96e915dd | ||
|
|
7530d3ff8e | ||
|
|
6c3b07f56d | ||
|
|
8833e41511 | ||
|
|
d3d03868d8 | ||
|
|
7d853494ec | ||
|
|
602f8d07f9 | ||
|
|
2dfcb49b34 | ||
|
|
5eb4e0fee5 | ||
|
|
af9a51603b | ||
|
|
62e23e2e80 | ||
|
|
e5fc4f5a9c | ||
|
|
8ca2b1de52 | ||
|
|
6dd79895e2 | ||
|
|
333f0d72a1 | ||
|
|
5a21561d6e | ||
|
|
ee8016b3d9 | ||
|
|
2a584836b8 | ||
|
|
3d328268f0 | ||
|
|
26f116af70 | ||
|
|
a08a6fc7d9 | ||
|
|
d311fad538 | ||
|
|
a18be650b8 | ||
|
|
72013390bd | ||
|
|
93547ce7ea | ||
|
|
dbf5ea5935 | ||
|
|
fa1fd6965e | ||
|
|
1f7e47f747 | ||
|
|
2d9dc7f75a | ||
|
|
f93dc55a55 | ||
|
|
46ec2d1b46 | ||
|
|
b413afd522 | ||
|
|
702082e4c9 | ||
|
|
e5f1194a6d | ||
|
|
ef4f103ee8 | ||
|
|
1925612f1b | ||
|
|
86f75b05ea | ||
|
|
35546fd856 | ||
|
|
9ce407fec0 | ||
|
|
bbe870ff59 | ||
|
|
af321b8ff3 | ||
|
|
50b9f19d3c | ||
|
|
1f82078e74 | ||
|
|
37c08332d5 | ||
|
|
a94ff15ebd | ||
|
|
4bd9f36182 | ||
|
|
e0ee3458d0 | ||
|
|
7390335b4a | ||
|
|
8d05e6d2df | ||
|
|
00421bfc9f | ||
|
|
2949994fbd | ||
|
|
037dcb0f1a | ||
|
|
bd948bcbba | ||
|
|
3c7afadab3 | ||
|
|
9db2c9471f | ||
|
|
4161360cca | ||
|
|
7b7fa87a53 | ||
|
|
170f62ca89 | ||
|
|
c273f66392 | ||
|
|
95e7ca284d | ||
|
|
fe3f617afc | ||
|
|
2d68476867 | ||
|
|
4ebdc0ea49 | ||
|
|
174ce2f65c | ||
|
|
3de96ca51c | ||
|
|
0e506a936d | ||
|
|
d276f739f2 | ||
|
|
7d635329c3 | ||
|
|
c105f31fd7 | ||
|
|
0fce9519e7 | ||
|
|
6db9bc31b4 | ||
|
|
03b52391bd | ||
|
|
094ae26c7e | ||
|
|
eb54e2293e | ||
|
|
acdf544694 | ||
|
|
1b81f4c2ef | ||
|
|
8a7b7b51e2 | ||
|
|
fc3eaec6f4 | ||
|
|
09e026d43f | ||
|
|
c0271bdccf | ||
|
|
9b27f353a9 | ||
|
|
aaeef297a9 | ||
|
|
5fc009edbd | ||
|
|
12d0fa4c39 | ||
|
|
cff4614c69 | ||
|
|
711e544581 | ||
|
|
c7441251a7 | ||
|
|
a79e43bb1b | ||
|
|
997223d0a5 | ||
|
|
8b070b5a31 | ||
|
|
10acf7b09c | ||
|
|
1c7d767977 | ||
|
|
c867f0675e | ||
|
|
86b2dde8e4 | ||
|
|
d3d228f19d | ||
|
|
e8f37d79aa | ||
|
|
93d3fe60ae | ||
|
|
fba15018bd | ||
|
|
57562de259 | ||
|
|
3b8b4c16dd | ||
|
|
ff20becb13 | ||
|
|
67a13bbe6e | ||
|
|
140fbccd09 | ||
|
|
b9cbfadf15 | ||
|
|
7ac6a6b500 | ||
|
|
afe1eaad6a | ||
|
|
bb21fcb3db | ||
|
|
73be2c9912 | ||
|
|
158e2883f6 | ||
|
|
0137050ef5 | ||
|
|
21ea86a827 | ||
|
|
524032ee39 | ||
|
|
5bb97700c4 | ||
|
|
0a170ca457 | ||
|
|
1523d89df8 | ||
|
|
0c7de210ae | ||
|
|
2c7d99b9ae | ||
|
|
4b53a60b11 | ||
|
|
114a4bf140 | ||
|
|
b20dbf5cf5 | ||
|
|
02bba2397b | ||
|
|
cb21483053 | ||
|
|
279a58b05b | ||
|
|
e01509a31f | ||
|
|
b40b62f1b2 | ||
|
|
ad49186498 | ||
|
|
f000a73a3a | ||
|
|
2a6f07aea2 | ||
|
|
16042390d7 | ||
|
|
70a9f9047e | ||
|
|
0366e0153d | ||
|
|
0a34eb18e8 | ||
|
|
787dccd85a | ||
|
|
16b2fd3cc9 | ||
|
|
b72f6df58a | ||
|
|
91fcd86bf1 | ||
|
|
2c1a8a9a22 | ||
|
|
64b29fee1b | ||
|
|
148d8da7a9 | ||
|
|
f13bb9e2e9 | ||
|
|
1e6a4c6128 | ||
|
|
2873aa43f4 | ||
|
|
e7b4256474 | ||
|
|
b0b8353e26 | ||
|
|
2848ca2607 | ||
|
|
9b7445cb18 | ||
|
|
9b2cdc2ccf | ||
|
|
301b80b512 | ||
|
|
504da424c2 | ||
|
|
24cc8578fd | ||
|
|
b895da4577 | ||
|
|
5a62dbd336 | ||
|
|
c7a0978271 | ||
|
|
872e1f2f5a | ||
|
|
fe565dc2d9 | ||
|
|
1b65c3f330 | ||
|
|
fdd2e982c5 | ||
|
|
49235d5525 | ||
|
|
0cf224c958 | ||
|
|
664224fe14 | ||
|
|
351cab4c2a | ||
|
|
c615c8a56d | ||
|
|
048275cb93 | ||
|
|
df8f70aac8 | ||
|
|
40ce041aa7 | ||
|
|
68df7d8dda | ||
|
|
d9004fc748 | ||
|
|
84680ba0ff | ||
|
|
9f5111d474 | ||
|
|
cd3ce58df4 | ||
|
|
39f9565e34 | ||
|
|
2d9f13d0bb | ||
|
|
b735c00761 | ||
|
|
aae9a24cbd | ||
|
|
dde489db1c | ||
|
|
d0b20b06dc | ||
|
|
2ccb96086f | ||
|
|
ba4c562508 | ||
|
|
701b3403c5 | ||
|
|
f927aaeddd | ||
|
|
e0ffec8a63 | ||
|
|
a04d634fba | ||
|
|
0b076c13a0 | ||
|
|
d33981ffa2 | ||
|
|
2b62aa7d92 | ||
|
|
dfbcf757b9 | ||
|
|
d5feafc654 | ||
|
|
f47a5f90c2 | ||
|
|
faa01bb6f9 | ||
|
|
1e1a744604 | ||
|
|
aca9ed2030 | ||
|
|
b83554fca8 | ||
|
|
3275337a26 | ||
|
|
89b6fe01a6 | ||
|
|
1fe1a0060b | ||
|
|
de72cb1686 | ||
|
|
2e24cdc7ea | ||
|
|
ebf367497b | ||
|
|
277c6adaf1 | ||
|
|
20ce840a2d | ||
|
|
20776419d8 | ||
|
|
2bf51f401d | ||
|
|
6a4468ce6b |
18
.github/dependabot.yml
vendored
18
.github/dependabot.yml
vendored
@@ -1,18 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
ignore:
|
||||
- dependency-name: k8s.io/*
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/ui/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
77
.github/workflows/ci-build.yaml
vendored
77
.github/workflows/ci-build.yaml
vendored
@@ -9,10 +9,11 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'release-*'
|
||||
|
||||
env:
|
||||
# Golang version to use across CI steps
|
||||
GOLANG_VERSION: '1.18'
|
||||
GOLANG_VERSION: '1.19'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -27,9 +28,9 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f # v3.3.1
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Download all Go modules
|
||||
@@ -45,13 +46,13 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f # v3.3.1
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Restore go build cache
|
||||
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
||||
@@ -69,9 +70,9 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f # v3.3.1
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Run golangci-lint
|
||||
@@ -92,11 +93,11 @@ jobs:
|
||||
- name: Create checkout directory
|
||||
run: mkdir -p ~/go/src/github.com/argoproj
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
- name: Create symlink in GOPATH
|
||||
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f # v3.3.1
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Install required packages
|
||||
@@ -116,13 +117,17 @@ jobs:
|
||||
run: |
|
||||
echo "/usr/local/bin" >> $GITHUB_PATH
|
||||
- name: Restore go build cache
|
||||
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
||||
- name: Install all tools required for building & testing
|
||||
run: |
|
||||
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
|
||||
run: |
|
||||
git config --global user.name "John Doe"
|
||||
@@ -133,12 +138,12 @@ jobs:
|
||||
- name: Run all unit tests
|
||||
run: make test-local
|
||||
- name: Generate code coverage artifacts
|
||||
uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: code-coverage
|
||||
path: coverage.out
|
||||
- name: Generate test results artifacts
|
||||
uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: test-results
|
||||
path: test-results/
|
||||
@@ -155,11 +160,11 @@ jobs:
|
||||
- name: Create checkout directory
|
||||
run: mkdir -p ~/go/src/github.com/argoproj
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
- name: Create symlink in GOPATH
|
||||
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f # v3.3.1
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Install required packages
|
||||
@@ -179,13 +184,17 @@ jobs:
|
||||
run: |
|
||||
echo "/usr/local/bin" >> $GITHUB_PATH
|
||||
- name: Restore go build cache
|
||||
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
||||
- name: Install all tools required for building & testing
|
||||
run: |
|
||||
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
|
||||
run: |
|
||||
git config --global user.name "John Doe"
|
||||
@@ -196,7 +205,7 @@ jobs:
|
||||
- name: Run all unit tests
|
||||
run: make test-race-local
|
||||
- name: Generate test results artifacts
|
||||
uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: race-results
|
||||
path: test-results/
|
||||
@@ -206,9 +215,9 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f # v3.3.1
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Create symlink in GOPATH
|
||||
@@ -232,6 +241,10 @@ jobs:
|
||||
make install-codegen-tools-local
|
||||
make install-go-tools-local
|
||||
working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
|
||||
# We install kustomize in the dist directory
|
||||
- name: Add dist to PATH
|
||||
run: |
|
||||
echo "/home/runner/work/argo-cd/argo-cd/dist" >> $GITHUB_PATH
|
||||
- name: Run codegen
|
||||
run: |
|
||||
set -x
|
||||
@@ -250,14 +263,14 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # v3.5.1
|
||||
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
||||
with:
|
||||
node-version: '12.18.4'
|
||||
- name: Restore node dependency cache
|
||||
id: cache-dependencies
|
||||
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
with:
|
||||
path: ui/node_modules
|
||||
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
|
||||
@@ -287,12 +300,12 @@ jobs:
|
||||
sonar_secret: ${{ secrets.SONAR_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Restore node dependency cache
|
||||
id: cache-dependencies
|
||||
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
with:
|
||||
path: ui/node_modules
|
||||
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
|
||||
@@ -303,11 +316,11 @@ jobs:
|
||||
run: |
|
||||
mkdir -p test-results
|
||||
- name: Get code coverage artifiact
|
||||
uses: actions/download-artifact@9782bd6a9848b53b110e712e20e42d89988822b7 # v3.0.1
|
||||
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
|
||||
with:
|
||||
name: code-coverage
|
||||
- name: Get test result artifact
|
||||
uses: actions/download-artifact@9782bd6a9848b53b110e712e20e42d89988822b7 # v3.0.1
|
||||
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
|
||||
with:
|
||||
name: test-results
|
||||
path: test-results
|
||||
@@ -366,9 +379,9 @@ jobs:
|
||||
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f # v3.3.1
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: GH actions workaround - Kill XSP4 process
|
||||
@@ -386,7 +399,7 @@ jobs:
|
||||
sudo chown runner $HOME/.kube/config
|
||||
kubectl version
|
||||
- name: Restore go build cache
|
||||
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
||||
@@ -412,9 +425,9 @@ jobs:
|
||||
git config --global user.email "john.doe@example.com"
|
||||
- name: Pull Docker image required for tests
|
||||
run: |
|
||||
docker pull ghcr.io/dexidp/dex:v2.35.3
|
||||
docker pull ghcr.io/dexidp/dex:v2.37.0
|
||||
docker pull argoproj/argo-cd-ci-builder:v1.0.0
|
||||
docker pull redis:7.0.5-alpine
|
||||
docker pull redis:7.0.11-alpine
|
||||
- name: Create target directory for binaries in the build-process
|
||||
run: |
|
||||
mkdir -p dist
|
||||
@@ -442,7 +455,7 @@ jobs:
|
||||
set -x
|
||||
make test-e2e-local
|
||||
- name: Upload e2e-server logs
|
||||
uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: e2e-server-k8s${{ matrix.k3s-version }}.log
|
||||
path: /tmp/e2e-server.log
|
||||
|
||||
3
.github/workflows/codeql.yml
vendored
3
.github/workflows/codeql.yml
vendored
@@ -5,6 +5,7 @@ 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
|
||||
branches-ignore:
|
||||
- 'dependabot/**'
|
||||
- 'cherry-pick-*'
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 19 * * 0'
|
||||
@@ -29,7 +30,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
32
.github/workflows/image.yaml
vendored
32
.github/workflows/image.yaml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
types: [ labeled, unlabeled, opened, synchronize, reopened ]
|
||||
|
||||
env:
|
||||
GOLANG_VERSION: '1.18'
|
||||
GOLANG_VERSION: '1.19'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -23,16 +23,15 @@ jobs:
|
||||
publish:
|
||||
permissions:
|
||||
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'
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
GOPATH: /home/runner/work/argo-cd/argo-cd
|
||||
steps:
|
||||
- uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f # v3.3.1
|
||||
- uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
|
||||
- uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
with:
|
||||
path: src/github.com/argoproj/argo-cd
|
||||
|
||||
@@ -47,14 +46,14 @@ jobs:
|
||||
docker login quay.io --username "$DOCKER_USERNAME" --password-stdin <<< "$DOCKER_TOKEN"
|
||||
if: github.event_name == 'push'
|
||||
env:
|
||||
USERNAME: ${{ github.actor }}
|
||||
PASSWORD: ${{ secrets.GITHUB_TOKEN }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
PASSWORD: ${{ secrets.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@8c0edbc76e98fa90f69d9a2c020dcb50019dc325 # v2.2.1
|
||||
- uses: docker/setup-buildx-action@f03ac48505955848960e80bbb68046aa35c7b9e7 # v2.4.1
|
||||
- run: |
|
||||
IMAGE_PLATFORMS=linux/amd64
|
||||
if [[ "${{ github.event_name }}" == "push" || "${{ contains(github.event.pull_request.labels.*.name, 'test-arm-image') }}" == "true" ]]
|
||||
@@ -62,20 +61,27 @@ jobs:
|
||||
IMAGE_PLATFORMS=linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
|
||||
fi
|
||||
echo "Building image for platforms: $IMAGE_PLATFORMS"
|
||||
docker buildx build --platform $IMAGE_PLATFORMS --push="${{ github.event_name == 'push' }}" \
|
||||
-t ghcr.io/argoproj/argo-cd/argocd:${{ steps.image.outputs.tag }} \
|
||||
docker buildx build --platform $IMAGE_PLATFORMS --sbom=false --provenance=false --push="${{ github.event_name == 'push' }}" \
|
||||
-t ghcr.io/argoproj/argocd:${{ steps.image.outputs.tag }} \
|
||||
-t quay.io/argoproj/argocd:latest .
|
||||
working-directory: ./src/github.com/argoproj/argo-cd
|
||||
|
||||
# sign container images
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@9becc617647dfa20ae7b1151972e9b3a2c338a2b # v2.8.1
|
||||
uses: sigstore/cosign-installer@c3667d99424e7e6047999fb6246c0da843953c65 # v3.0.1
|
||||
with:
|
||||
cosign-release: 'v1.13.0'
|
||||
cosign-release: 'v1.13.1'
|
||||
|
||||
- name: Install crane to get digest of image
|
||||
uses: imjasonh/setup-crane@00c9e93efa4e1138c9a7a5c594acd6c75a2fbf0c
|
||||
|
||||
- name: Get digest of image
|
||||
run: |
|
||||
echo "IMAGE_DIGEST=$(crane digest quay.io/argoproj/argocd:latest)" >> $GITHUB_ENV
|
||||
|
||||
- name: Sign Argo CD latest image
|
||||
run: |
|
||||
cosign sign --key env://COSIGN_PRIVATE_KEY quay.io/argoproj/argocd:latest
|
||||
cosign sign --key env://COSIGN_PRIVATE_KEY quay.io/argoproj/argocd@${{ env.IMAGE_DIGEST }}
|
||||
# Displays the public key to share.
|
||||
cosign public-key --key env://COSIGN_PRIVATE_KEY
|
||||
env:
|
||||
@@ -89,7 +95,7 @@ jobs:
|
||||
env:
|
||||
TOKEN: ${{ secrets.TOKEN }}
|
||||
- run: |
|
||||
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 }}
|
||||
docker run -u $(id -u):$(id -g) -v $(pwd):/src -w /src --rm -t ghcr.io/argoproj/argocd:${{ steps.image.outputs.tag }} kustomize edit set image quay.io/argoproj/argocd=ghcr.io/argoproj/argocd:${{ steps.image.outputs.tag }}
|
||||
git config --global user.email 'ci@argoproj.com'
|
||||
git config --global user.name 'CI'
|
||||
git diff --exit-code && echo 'Already deployed' || (git commit -am 'Upgrade argocd to ${{ steps.image.outputs.tag }}' && git push)
|
||||
|
||||
39
.github/workflows/release.yaml
vendored
39
.github/workflows/release.yaml
vendored
@@ -12,7 +12,7 @@ on:
|
||||
- "!release-v0*"
|
||||
|
||||
env:
|
||||
GOLANG_VERSION: '1.18'
|
||||
GOLANG_VERSION: '1.19'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
GIT_EMAIL: argoproj@gmail.com
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
echo "RELEASE_NOTES=${RELEASE_NOTES}" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f # v3.3.1
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
|
||||
@@ -177,6 +177,10 @@ jobs:
|
||||
run: |
|
||||
set -ue
|
||||
make install-codegen-tools-local
|
||||
|
||||
# We install kustomize in the dist directory
|
||||
echo "/home/runner/work/argo-cd/argo-cd/dist" >> $GITHUB_PATH
|
||||
|
||||
make manifests-local VERSION=${TARGET_VERSION}
|
||||
git diff
|
||||
git commit manifests/ -m "Bump version to ${TARGET_VERSION}"
|
||||
@@ -201,13 +205,13 @@ jobs:
|
||||
if: ${{ env.DRY_RUN != 'true' }}
|
||||
|
||||
- uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0
|
||||
- uses: docker/setup-buildx-action@8c0edbc76e98fa90f69d9a2c020dcb50019dc325 # v2.2.1
|
||||
- uses: docker/setup-buildx-action@f03ac48505955848960e80bbb68046aa35c7b9e7 # v2.4.1
|
||||
- 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 --push -t ${IMAGE_NAMESPACE}/argocd:v${TARGET_VERSION} -t argoproj/argocd:v${TARGET_VERSION} .
|
||||
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
|
||||
@@ -215,13 +219,20 @@ jobs:
|
||||
if: ${{ env.DRY_RUN != 'true' }}
|
||||
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@9becc617647dfa20ae7b1151972e9b3a2c338a2b # v2.8.1
|
||||
uses: sigstore/cosign-installer@c3667d99424e7e6047999fb6246c0da843953c65 # v3.0.1
|
||||
with:
|
||||
cosign-release: 'v1.13.0'
|
||||
cosign-release: 'v1.13.1'
|
||||
|
||||
- name: Install crane to get digest of image
|
||||
uses: imjasonh/setup-crane@00c9e93efa4e1138c9a7a5c594acd6c75a2fbf0c
|
||||
|
||||
- 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:v${TARGET_VERSION}
|
||||
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
|
||||
@@ -255,6 +266,14 @@ jobs:
|
||||
body: ${{ steps.release-notes.outputs.content }}
|
||||
if: ${{ env.DRY_RUN == 'true' }}
|
||||
|
||||
# Based on this suggestion: https://github.com/actions/runner-images/issues/2840#issuecomment-1284059930
|
||||
- name: Free Up Disk Space
|
||||
id: free-up-disk-space
|
||||
run: |
|
||||
df -h
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
df -h
|
||||
|
||||
- name: Generate SBOM (spdx)
|
||||
id: spdx-builder
|
||||
env:
|
||||
@@ -264,7 +283,7 @@ jobs:
|
||||
SIGS_BOM_VERSION: v0.2.1
|
||||
# comma delimited list of project relative folders to inspect for package
|
||||
# managers (gomod, yarn, npm).
|
||||
PROJECT_FOLDERS: ".,./ui"
|
||||
PROJECT_FOLDERS: ".,./ui"
|
||||
# full qualified name of the docker image to be inspected
|
||||
DOCKER_IMAGE: ${{env.IMAGE_NAMESPACE}}/argocd:v${{env.TARGET_VERSION}}
|
||||
run: |
|
||||
@@ -295,7 +314,7 @@ jobs:
|
||||
if: ${{ env.DRY_RUN != 'true' }}
|
||||
|
||||
- name: Create GitHub release
|
||||
uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5 # v0.1.14
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
17
.github/workflows/update-snyk.yaml
vendored
17
.github/workflows/update-snyk.yaml
vendored
@@ -1,6 +1,5 @@
|
||||
name: Snyk report update
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
schedule:
|
||||
- cron: '0 0 * * 0' # midnight every Sunday
|
||||
|
||||
@@ -10,27 +9,23 @@ permissions:
|
||||
jobs:
|
||||
snyk-report:
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
contents: write # To push snyk reports
|
||||
if: github.repository == 'argoproj/argo-cd'
|
||||
name: Update Snyk report in the docs directory
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build reports
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
run: |
|
||||
make snyk-report
|
||||
pr_branch="snyk-update-$(echo $RANDOM | md5sum | head -c 20)"
|
||||
git checkout -b "$pr_branch"
|
||||
git config --global user.email 'ci@argoproj.com'
|
||||
git config --global user.name 'CI'
|
||||
git add docs/snyk
|
||||
git commit -m "[Bot] Update Snyk reports" --signoff
|
||||
git push --set-upstream origin "$pr_branch"
|
||||
gh pr create -B master -H "$pr_branch" --title '[Bot] docs: Update Snyk report' --body ''
|
||||
git add docs/snyk/index.md
|
||||
git add docs/snyk/*/*.html
|
||||
git commit -m "[Bot] Update Snyk reports"
|
||||
git push
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,7 +17,6 @@ test-results
|
||||
node_modules/
|
||||
.kube/
|
||||
./test/cmp/*.sock
|
||||
.envrc.remote
|
||||
|
||||
# ignore built binaries
|
||||
cmd/argocd/argocd
|
||||
|
||||
@@ -4,7 +4,7 @@ ARG BASE_IMAGE=docker.io/library/ubuntu:22.04
|
||||
# Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image
|
||||
# Also used as the image in CI jobs so needs all dependencies
|
||||
####################################################################################################
|
||||
FROM docker.io/library/golang:1.18 AS builder
|
||||
FROM docker.io/library/golang:1.19 AS builder
|
||||
|
||||
RUN echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list
|
||||
|
||||
@@ -99,7 +99,7 @@ RUN HOST_ARCH=$TARGETARCH NODE_ENV='production' NODE_ONLINE_ENV='online' NODE_OP
|
||||
####################################################################################################
|
||||
# Argo CD Build stage which performs the actual build of Argo CD binaries
|
||||
####################################################################################################
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.18 AS argocd-build
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.19 AS argocd-build
|
||||
|
||||
WORKDIR /go/src/github.com/argoproj/argo-cd
|
||||
|
||||
|
||||
2
OWNERS
2
OWNERS
@@ -27,5 +27,3 @@ reviewers:
|
||||
- wanghong230
|
||||
- ciiay
|
||||
- saumeya
|
||||
- zachaller
|
||||
- 34fathombelow
|
||||
|
||||
12
README.md
12
README.md
@@ -1,5 +1,4 @@
|
||||
[](https://github.com/argoproj/argo-cd/actions?query=workflow%3A%22Integration+tests%22) [](https://argoproj.github.io/community/join-slack) [](https://codecov.io/gh/argoproj/argo-cd) [](https://github.com/argoproj/argo-cd/releases/latest) [](https://bestpractices.coreinfrastructure.org/projects/4486) [](https://twitter.com/argoproj)
|
||||
[](https://artifacthub.io/packages/helm/argo/argo-cd)
|
||||
|
||||
# Argo CD - Declarative Continuous Delivery for Kubernetes
|
||||
|
||||
@@ -43,6 +42,7 @@ Participation in the Argo CD project is governed by the [CNCF Code of Conduct](h
|
||||
### Blogs and Presentations
|
||||
|
||||
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://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. [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)
|
||||
@@ -50,8 +50,10 @@ Participation in the Argo CD project is governed by the [CNCF Code of Conduct](h
|
||||
1. [Automation of Everything - How To Combine Argo Events, Workflows & Pipelines, CD, and Rollouts](https://youtu.be/XNXJtxkUKeY)
|
||||
1. [Environments Based On Pull Requests (PRs): Using Argo CD To Apply GitOps Principles On Previews](https://youtu.be/cpAaI8p4R60)
|
||||
1. [Argo CD: Applying GitOps Principles To Manage Production Environment In Kubernetes](https://youtu.be/vpWQeoaiRM4)
|
||||
1. [Creating Temporary Preview Environments Based On Pull Requests With Argo CD And Codefresh](https://codefresh.io/continuous-deployment/creating-temporary-preview-environments-based-pull-requests-argo-cd-codefresh/)
|
||||
1. [Tutorial: Everything You Need To Become A GitOps Ninja](https://www.youtube.com/watch?v=r50tRQjisxw) 90m tutorial on GitOps and Argo CD.
|
||||
1. [Comparison of Argo CD, Spinnaker, Jenkins X, and Tekton](https://www.inovex.de/blog/spinnaker-vs-argo-cd-vs-tekton-vs-jenkins-x/)
|
||||
1. [Simplify and Automate Deployments Using GitOps with IBM Multicloud Manager 3.1.2](https://www.ibm.com/cloud/blog/simplify-and-automate-deployments-using-gitops-with-ibm-multicloud-manager-3-1-2)
|
||||
1. [GitOps for Kubeflow using Argo CD](https://v0-6.kubeflow.org/docs/use-cases/gitops-for-kubeflow/)
|
||||
1. [GitOps Toolsets on Kubernetes with CircleCI and Argo CD](https://www.digitalocean.com/community/tutorials/webinar-series-gitops-tool-sets-on-kubernetes-with-circleci-and-argo-cd)
|
||||
1. [CI/CD in Light Speed with K8s and Argo CD](https://www.youtube.com/watch?v=OdzH82VpMwI&feature=youtu.be)
|
||||
@@ -60,6 +62,7 @@ Participation in the Argo CD project is governed by the [CNCF Code of Conduct](h
|
||||
1. [Introduction to Argo CD : Kubernetes DevOps CI/CD](https://www.youtube.com/watch?v=2WSJF7d8dUg&feature=youtu.be)
|
||||
1. [GitOps Deployment and Kubernetes - using Argo CD](https://medium.com/riskified-technology/gitops-deployment-and-kubernetes-f1ab289efa4b)
|
||||
1. [Deploy Argo CD with Ingress and TLS in Three Steps: No YAML Yak Shaving Required](https://itnext.io/deploy-argo-cd-with-ingress-and-tls-in-three-steps-no-yaml-yak-shaving-required-bc536d401491)
|
||||
1. [GitOps Continuous Delivery with Argo and Codefresh](https://codefresh.io/events/cncf-member-webinar-gitops-continuous-delivery-argo-codefresh/)
|
||||
1. [Stay up to date with Argo CD and Renovate](https://mjpitz.com/blog/2020/12/03/renovate-your-gitops/)
|
||||
1. [Setting up Argo CD with Helm](https://www.arthurkoziel.com/setting-up-argocd-with-helm/)
|
||||
1. [Applied GitOps with Argo CD](https://thenewstack.io/applied-gitops-with-argocd/)
|
||||
@@ -69,10 +72,3 @@ Participation in the Argo CD project is governed by the [CNCF Code of Conduct](h
|
||||
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)
|
||||
|
||||
### Vendor-sponsored community resources
|
||||
|
||||
The following links are to resources hosted by Argo CD vendors (companies which use Argo CD as part of their software offerings).
|
||||
These resources are vendor-neutral (overwhelmingly _about_ Argo CD, but may briefly mention the vendor) and do not require providing information for marketing purposes.
|
||||
|
||||
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. [Argo CD Sandbox (demo instance)](https://argocd.argo.opsmx.net/) (requires logging into the demo instance with a Google account)
|
||||
|
||||
20
USERS.md
20
USERS.md
@@ -11,11 +11,9 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Adevinta](https://www.adevinta.com/)
|
||||
1. [Adfinis](https://adfinis.com)
|
||||
1. [Adventure](https://jp.adventurekk.com/)
|
||||
1. [AirQo](https://airqo.net/)
|
||||
1. [Akuity](https://akuity.io/)
|
||||
1. [Alibaba Group](https://www.alibabagroup.com/)
|
||||
1. [Allianz Direct](https://www.allianzdirect.de/)
|
||||
1. [Amadeus IT Group](https://amadeus.com/)
|
||||
1. [Ambassador Labs](https://www.getambassador.io/)
|
||||
1. [ANSTO - Australian Synchrotron](https://www.synchrotron.org.au/)
|
||||
1. [Ant Group](https://www.antgroup.com/)
|
||||
@@ -23,6 +21,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Arctiq Inc.](https://www.arctiq.ca)
|
||||
1. [ARZ Allgemeines Rechenzentrum GmbH](https://www.arz.at/)
|
||||
1. [Axual B.V.](https://axual.com)
|
||||
1. [Back Market](https://www.backmarket.com)
|
||||
1. [Baloise](https://www.baloise.com)
|
||||
1. [BCDevExchange DevOps Platform](https://bcdevexchange.org/DevOpsPlatform)
|
||||
1. [Beat](https://thebeat.co/en/)
|
||||
@@ -37,7 +36,6 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Camptocamp](https://camptocamp.com)
|
||||
1. [Capital One](https://www.capitalone.com)
|
||||
1. [CARFAX](https://www.carfax.com)
|
||||
1. [CARFAX Europe](https://www.carfax.eu)
|
||||
1. [Casavo](https://casavo.com)
|
||||
1. [Celonis](https://www.celonis.com/)
|
||||
1. [CERN](https://home.cern/)
|
||||
@@ -56,11 +54,8 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [D2iQ](https://www.d2iq.com)
|
||||
1. [Datarisk](https://www.datarisk.io/)
|
||||
1. [Deloitte](https://www.deloitte.com/)
|
||||
1. [Deutsche Telekom AG](https://telekom.com)
|
||||
1. [Devopsi - Poland Software/DevOps Consulting](https://devopsi.pl/)
|
||||
1. [Devtron Labs](https://github.com/devtron-labs/devtron)
|
||||
1. [Divistant](https://divistant.com)
|
||||
1. [Doximity](https://www.doximity.com/)
|
||||
1. [EDF Renewables](https://www.edf-re.com/)
|
||||
1. [edX](https://edx.org)
|
||||
1. [Elastic](https://elastic.co/)
|
||||
@@ -70,7 +65,6 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [END.](https://www.endclothing.com/)
|
||||
1. [Energisme](https://energisme.com/)
|
||||
1. [enigmo](https://enigmo.co.jp/)
|
||||
1. [Envoy](https://envoy.com/)
|
||||
1. [Faro](https://www.faro.com/)
|
||||
1. [Fave](https://myfave.com)
|
||||
1. [Flip](https://flip.id)
|
||||
@@ -81,12 +75,12 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [G DATA CyberDefense AG](https://www.gdata-software.com/)
|
||||
1. [Garner](https://www.garnercorp.com)
|
||||
1. [Generali Deutschland AG](https://www.generali.de/)
|
||||
1. [Gepardec](https://gepardec.com/)
|
||||
1. [Gitpod](https://www.gitpod.io)
|
||||
1. [Gllue](https://gllue.com)
|
||||
1. [gloat](https://gloat.com/)
|
||||
1. [GLOBIS](https://globis.com)
|
||||
1. [Glovo](https://www.glovoapp.com)
|
||||
1. [GlueOps](https://glueops.dev)
|
||||
1. [GMETRI](https://gmetri.com/)
|
||||
1. [Gojek](https://www.gojek.io/)
|
||||
1. [Greenpass](https://www.greenpass.com.br/)
|
||||
@@ -104,6 +98,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [IITS-Consulting](https://iits-consulting.de)
|
||||
1. [imaware](https://imaware.health)
|
||||
1. [Index Exchange](https://www.indexexchange.com/)
|
||||
1. [Info Support](https://www.infosupport.com/)
|
||||
1. [InsideBoard](https://www.insideboard.com)
|
||||
1. [Intuit](https://www.intuit.com/)
|
||||
1. [Joblift](https://joblift.com/)
|
||||
@@ -122,7 +117,6 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Kurly](https://www.kurly.com/)
|
||||
1. [LexisNexis](https://www.lexisnexis.com/)
|
||||
1. [Lian Chu Securities](https://lczq.com)
|
||||
1. [Liatrio](https://www.liatrio.com)
|
||||
1. [Lightricks](https://www.lightricks.com/)
|
||||
1. [LINE](https://linecorp.com/en/)
|
||||
1. [Lytt](https://www.lytt.co/)
|
||||
@@ -149,7 +143,6 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Nextdoor](https://nextdoor.com/)
|
||||
1. [Nikkei](https://www.nikkei.co.jp/nikkeiinfo/en/)
|
||||
1. [Nitro](https://gonitro.com)
|
||||
1. [Objective](https://www.objective.com.br/)
|
||||
1. [OCCMundial](https://occ.com.mx)
|
||||
1. [Octadesk](https://octadesk.com)
|
||||
1. [omegaUp](https://omegaUp.com)
|
||||
@@ -165,9 +158,9 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Packlink](https://www.packlink.com/)
|
||||
1. [Pandosearch](https://www.pandosearch.com/en/home)
|
||||
1. [PagerDuty](https://www.pagerduty.com/)
|
||||
1. [Patreon](https://www.patreon.com/)
|
||||
1. [PayPay](https://paypay.ne.jp/)
|
||||
1. [Peloton Interactive](https://www.onepeloton.com/)
|
||||
1. [Pigment](https://www.gopigment.com/)
|
||||
1. [Pipefy](https://www.pipefy.com/)
|
||||
1. [Pismo](https://pismo.io/)
|
||||
1. [Polarpoint.io](https://polarpoint.io)
|
||||
@@ -182,6 +175,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [RapidAPI](https://www.rapidapi.com/)
|
||||
1. [Recreation.gov](https://www.recreation.gov/)
|
||||
1. [Red Hat](https://www.redhat.com/)
|
||||
1. [Redpill Linpro](https://www.redpill-linpro.com/)
|
||||
1. [reev.com](https://www.reev.com/)
|
||||
1. [RightRev](https://rightrev.com/)
|
||||
1. [Rise](https://www.risecard.eu/)
|
||||
@@ -192,9 +186,9 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Saloodo! GmbH](https://www.saloodo.com)
|
||||
1. [Sap Labs](http://sap.com)
|
||||
1. [Schwarz IT](https://jobs.schwarz/it-mission)
|
||||
1. [SI Analytics](https://si-analytics.ai)
|
||||
1. [Skit](https://skit.ai/)
|
||||
1. [Skyscanner](https://www.skyscanner.net/)
|
||||
1. [Smart Pension](https://www.smartpension.co.uk/)
|
||||
1. [Smilee.io](https://smilee.io)
|
||||
1. [Snapp](https://snapp.ir/)
|
||||
1. [Snyk](https://snyk.io/)
|
||||
@@ -231,6 +225,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [ungleich.ch](https://ungleich.ch/)
|
||||
1. [Unifonic Inc](https://www.unifonic.com/)
|
||||
1. [Universidad Mesoamericana](https://www.umes.edu.gt/)
|
||||
1. [Urbantz](https://urbantz.com/)
|
||||
1. [Viaduct](https://www.viaduct.ai/)
|
||||
1. [Virtuo](https://www.govirtuo.com/)
|
||||
1. [VISITS Technologies](https://visits.world/en)
|
||||
@@ -243,7 +238,6 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [WeMo Scooter](https://www.wemoscooter.com/)
|
||||
1. [Whitehat Berlin](https://whitehat.berlin) by Guido Maria Serra +Fenaroli
|
||||
1. [Witick](https://witick.io/)
|
||||
1. [Wolffun Game](https://www.wolffungame.com/)
|
||||
1. [WooliesX](https://wooliesx.com.au/)
|
||||
1. [Woolworths Group](https://www.woolworthsgroup.com.au/)
|
||||
1. [WSpot](https://www.wspot.com.br/)
|
||||
|
||||
@@ -484,7 +484,7 @@ func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
// ...and if so, return it
|
||||
return []string{owner.Name}
|
||||
}); err != nil {
|
||||
return fmt.Errorf("error setting up with manager: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
@@ -567,7 +567,7 @@ func (r *ApplicationSetReconciler) createInCluster(ctx context.Context, applicat
|
||||
var createApps []argov1alpha1.Application
|
||||
current, err := r.getCurrentApplications(ctx, applicationSet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting current applications: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
m := make(map[string]bool) // Will holds the app names that are current in the cluster
|
||||
@@ -608,13 +608,13 @@ func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, applicat
|
||||
// clusterList, err := argoDB.ListClusters(ctx)
|
||||
clusterList, err := utils.ListClusters(ctx, r.KubeClientset, applicationSet.Namespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing clusters: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Save current applications to be able to delete the ones that are not in appList
|
||||
current, err := r.getCurrentApplications(ctx, applicationSet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting current applications: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
m := make(map[string]bool) // Will holds the app names in appList for the deletion process
|
||||
@@ -718,7 +718,7 @@ func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx conte
|
||||
|
||||
err := r.Client.Update(ctx, app, &client.UpdateOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating finalizers: %w", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
@@ -46,7 +47,6 @@ type addRateLimitingInterface interface {
|
||||
}
|
||||
|
||||
func (h *clusterSecretEventHandler) queueRelatedAppGenerators(q addRateLimitingInterface, object client.Object) {
|
||||
|
||||
// Check for label, lookup all ApplicationSets that might match the cluster, queue them all
|
||||
if object.GetLabels()[generators.ArgoCDSecretTypeLabel] != generators.ArgoCDSecretTypeCluster {
|
||||
return
|
||||
@@ -73,6 +73,40 @@ func (h *clusterSecretEventHandler) queueRelatedAppGenerators(q addRateLimitingI
|
||||
foundClusterGenerator = true
|
||||
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 {
|
||||
|
||||
@@ -82,3 +116,42 @@ func (h *clusterSecretEventHandler) queueRelatedAppGenerators(q addRateLimitingI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return nestedGeneratorsHaveClusterGenerator(nestedMatrix.ToMatrixGenerator().Generators)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return nestedGeneratorsHaveClusterGenerator(nestedMerge.ToMergeGenerator().Generators)
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
@@ -163,7 +164,6 @@ func TestClusterEventHandler(t *testing.T) {
|
||||
{NamespacedName: types.NamespacedName{Namespace: "another-namespace", Name: "my-app-set"}},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "non-argo cd secret should not match",
|
||||
items: []argov1alpha1.ApplicationSet{
|
||||
@@ -189,6 +189,348 @@ func TestClusterEventHandler(t *testing.T) {
|
||||
},
|
||||
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{
|
||||
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
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{
|
||||
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
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{
|
||||
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
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{
|
||||
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
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{
|
||||
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
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{
|
||||
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
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{
|
||||
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
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{
|
||||
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRequests: []reconcile.Request{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
179
applicationset/controllers/requeue_after_test.go
Normal file
179
applicationset/controllers/requeue_after_test.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/generators"
|
||||
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestRequeueAfter(t *testing.T) {
|
||||
mockServer := argoCDServiceMock{}
|
||||
ctx := context.Background()
|
||||
scheme := runtime.NewScheme()
|
||||
err := argov1alpha1.AddToScheme(scheme)
|
||||
assert.Nil(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)
|
||||
|
||||
terminalGenerators := map[string]generators.Generator{
|
||||
"List": generators.NewListGenerator(),
|
||||
"Clusters": generators.NewClusterGenerator(k8sClient, ctx, appClientset, "argocd"),
|
||||
"Git": generators.NewGitGenerator(mockServer),
|
||||
"SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), generators.SCMAuthProviders{}),
|
||||
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, fakeDynClient, appClientset, "argocd"),
|
||||
"PullRequest": generators.NewPullRequestGenerator(k8sClient, generators.SCMAuthProviders{}),
|
||||
}
|
||||
|
||||
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()
|
||||
r := ApplicationSetReconciler{
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(0),
|
||||
Generators: topLevelGenerators,
|
||||
}
|
||||
|
||||
type args struct {
|
||||
appset *argov1alpha1.ApplicationSet
|
||||
}
|
||||
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{}}},
|
||||
},
|
||||
}}, 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},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, r.getMinRequeueAfter(tt.args.appset), "getMinRequeueAfter(%v)", tt.args.appset)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -51,6 +51,8 @@ func NewClusterGenerator(c client.Client, ctx context.Context, clientset kuberne
|
||||
return g
|
||||
}
|
||||
|
||||
// GetRequeueAfter never requeue the cluster generator because the `clusterSecretEventHandler` will requeue the appsets
|
||||
// when the cluster secrets change
|
||||
func (g *ClusterGenerator) GetRequeueAfter(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) time.Duration {
|
||||
return NoRequeueAfter
|
||||
}
|
||||
@@ -170,7 +172,7 @@ func appendTemplatedValues(clusterValues map[string]string, params map[string]in
|
||||
result, err := replaceTemplatedString(value, params, appSet)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error replacing templated String: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if appSet.Spec.GoTemplate {
|
||||
|
||||
@@ -2,12 +2,11 @@ package generators
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
||||
"github.com/jeremywohl/flatten"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
|
||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
@@ -25,11 +24,14 @@ type TransformResult struct {
|
||||
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{}) ([]TransformResult, error) {
|
||||
selector, err := metav1.LabelSelectorAsSelector(requestedGenerator.Selector)
|
||||
// This is a custom version of the `LabelSelectorAsSelector` that is in k8s.io/apimachinery. This has been copied
|
||||
// verbatim from that package, with the difference that we do not have any restrictions on label values. This is done
|
||||
// so that, among other things, we can match on cluster urls.
|
||||
selector, err := utils.LabelSelectorAsSelector(requestedGenerator.Selector)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing label selector: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := []TransformResult{}
|
||||
@@ -72,8 +74,17 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
|
||||
}
|
||||
var filterParams []map[string]interface{}
|
||||
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(keepOnlyStringValues(param))) {
|
||||
if requestedGenerator.Selector != nil && !selector.Matches(labels.Set(flatParam)) {
|
||||
continue
|
||||
}
|
||||
filterParams = append(filterParams, param)
|
||||
@@ -88,18 +99,6 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
|
||||
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 {
|
||||
var res []Generator
|
||||
|
||||
@@ -122,6 +121,20 @@ func GetRelevantGenerators(requestedGenerator *argoprojiov1alpha1.ApplicationSet
|
||||
return res
|
||||
}
|
||||
|
||||
func flattenParameters(in map[string]interface{}) (map[string]string, error) {
|
||||
flat, err := flatten.Flatten(in, "", flatten.DotStyle)
|
||||
if err != nil {
|
||||
return nil, 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) {
|
||||
// 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)
|
||||
@@ -132,27 +145,15 @@ func mergeGeneratorTemplate(g Generator, requestedGenerator *argoprojiov1alpha1.
|
||||
return *dest, err
|
||||
}
|
||||
|
||||
// Currently for Matrix Generator. Allows interpolating the matrix's 2nd child generator with values from the 1st child generator
|
||||
// InterpolateGenerator allows interpolating the matrix's 2nd child generator with values from the 1st child generator
|
||||
// "params" parameter is an array, where each index corresponds to a generator. Each index contains a map w/ that generator's parameters.
|
||||
func InterpolateGenerator(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, params map[string]interface{}, useGoTemplate bool) (argoprojiov1alpha1.ApplicationSetGenerator, error) {
|
||||
interpolatedGenerator := requestedGenerator.DeepCopy()
|
||||
tmplBytes, err := json.Marshal(interpolatedGenerator)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("requestedGenerator", interpolatedGenerator).Error("error marshalling requested generator for interpolation")
|
||||
return *interpolatedGenerator, err
|
||||
}
|
||||
|
||||
render := utils.Render{}
|
||||
replacedTmplStr, err := render.Replace(string(tmplBytes), params, useGoTemplate)
|
||||
interpolatedGenerator, err := render.RenderGeneratorParams(requestedGenerator, params, useGoTemplate)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("interpolatedGeneratorString", replacedTmplStr).Error("error interpolating generator with other generator's parameter")
|
||||
log.WithError(err).WithField("interpolatedGenerator", interpolatedGenerator).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
|
||||
}
|
||||
|
||||
@@ -6,9 +6,11 @@ import (
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
testutils "github.com/argoproj/argo-cd/v2/applicationset/utils/test"
|
||||
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -92,8 +94,160 @@ func TestMatchValues(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func emptyTemplate() argoprojiov1alpha1.ApplicationSetTemplate {
|
||||
return argoprojiov1alpha1.ApplicationSetTemplate{
|
||||
func TestMatchValuesGoTemplate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
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) {
|
||||
var listGenerator = NewListGenerator()
|
||||
var 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)
|
||||
|
||||
assert.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",
|
||||
}},
|
||||
},
|
||||
{
|
||||
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",
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, testCase.expected, results[0].Params)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func emptyTemplate() argov1alpha1.ApplicationSetTemplate {
|
||||
return argov1alpha1.ApplicationSetTemplate{
|
||||
Spec: argov1alpha1.ApplicationSpec{
|
||||
Project: "project",
|
||||
},
|
||||
@@ -150,8 +304,35 @@ func getMockClusterGenerator() Generator {
|
||||
},
|
||||
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{}
|
||||
for _, clientCluster := range clusters {
|
||||
runtimeClusters = append(runtimeClusters, clientCluster)
|
||||
}
|
||||
appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
|
||||
|
||||
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
|
||||
@@ -159,8 +340,8 @@ func getMockClusterGenerator() Generator {
|
||||
}
|
||||
|
||||
func getMockGitGenerator() Generator {
|
||||
argoCDServiceMock := argoCDServiceMock{mock: &mock.Mock{}}
|
||||
argoCDServiceMock.mock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return([]string{"app1", "app2", "app_3", "p1/app4"}, nil)
|
||||
argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
|
||||
argoCDServiceMock.Mock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return([]string{"app1", "app2", "app_3", "p1/app4"}, nil)
|
||||
var gitGenerator = NewGitGenerator(argoCDServiceMock)
|
||||
return gitGenerator
|
||||
}
|
||||
@@ -248,6 +429,60 @@ func TestInterpolateGenerator(t *testing.T) {
|
||||
Path: "{{server}}",
|
||||
}
|
||||
|
||||
requestedGenerator = &argoprojiov1alpha1.ApplicationSetGenerator{
|
||||
Git: &argoprojiov1alpha1.GitGenerator{
|
||||
Files: append([]argoprojiov1alpha1.GitFileGeneratorItem{}, fileNamePath, fileServerPath),
|
||||
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
|
||||
},
|
||||
}
|
||||
clusterGeneratorParams := map[string]interface{}{
|
||||
"name": "production_01/west", "server": "https://production-01.example.com",
|
||||
}
|
||||
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, false)
|
||||
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 TestInterpolateGenerator_go(t *testing.T) {
|
||||
requestedGenerator := &argoprojiov1alpha1.ApplicationSetGenerator{
|
||||
Clusters: &argoprojiov1alpha1.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)
|
||||
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 := argoprojiov1alpha1.GitFileGeneratorItem{
|
||||
Path: "{{.name}}",
|
||||
}
|
||||
fileServerPath := argoprojiov1alpha1.GitFileGeneratorItem{
|
||||
Path: "{{.server}}",
|
||||
}
|
||||
|
||||
requestedGenerator = &argoprojiov1alpha1.ApplicationSetGenerator{
|
||||
Git: &argoprojiov1alpha1.GitGenerator{
|
||||
Files: append([]argoprojiov1alpha1.GitFileGeneratorItem{}, fileNamePath, fileServerPath),
|
||||
|
||||
@@ -58,9 +58,9 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
|
||||
|
||||
var err error
|
||||
var res []map[string]interface{}
|
||||
if appSetGenerator.Git.Directories != nil {
|
||||
if len(appSetGenerator.Git.Directories) != 0 {
|
||||
res, err = g.generateParamsForGitDirectories(appSetGenerator, appSet.Spec.GoTemplate)
|
||||
} else if appSetGenerator.Git.Files != nil {
|
||||
} else if len(appSetGenerator.Git.Files) != 0 {
|
||||
res, err = g.generateParamsForGitFiles(appSetGenerator, appSet.Spec.GoTemplate)
|
||||
} else {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package generators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
@@ -9,6 +8,7 @@ import (
|
||||
"github.com/stretchr/testify/mock"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
testutils "github.com/argoproj/argo-cd/v2/applicationset/utils/test"
|
||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
@@ -20,33 +20,6 @@ import (
|
||||
// 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) {
|
||||
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
|
||||
foo:
|
||||
@@ -200,9 +173,9 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
|
||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
argoCDServiceMock := argoCDServiceMock{mock: &mock.Mock{}}
|
||||
argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
|
||||
|
||||
argoCDServiceMock.mock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
|
||||
argoCDServiceMock.Mock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
|
||||
|
||||
var gitGenerator = NewGitGenerator(argoCDServiceMock)
|
||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||
@@ -229,7 +202,7 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
|
||||
assert.Equal(t, testCaseCopy.expected, got)
|
||||
}
|
||||
|
||||
argoCDServiceMock.mock.AssertExpectations(t)
|
||||
argoCDServiceMock.Mock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -442,9 +415,9 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
|
||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
argoCDServiceMock := argoCDServiceMock{mock: &mock.Mock{}}
|
||||
argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
|
||||
|
||||
argoCDServiceMock.mock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
|
||||
argoCDServiceMock.Mock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
|
||||
|
||||
var gitGenerator = NewGitGenerator(argoCDServiceMock)
|
||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||
@@ -472,7 +445,7 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
|
||||
assert.Equal(t, testCaseCopy.expected, got)
|
||||
}
|
||||
|
||||
argoCDServiceMock.mock.AssertExpectations(t)
|
||||
argoCDServiceMock.Mock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -732,8 +705,8 @@ cluster:
|
||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
argoCDServiceMock := argoCDServiceMock{mock: &mock.Mock{}}
|
||||
argoCDServiceMock.mock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
||||
argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
|
||||
argoCDServiceMock.Mock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
|
||||
|
||||
var gitGenerator = NewGitGenerator(argoCDServiceMock)
|
||||
@@ -762,7 +735,7 @@ cluster:
|
||||
assert.ElementsMatch(t, testCaseCopy.expected, got)
|
||||
}
|
||||
|
||||
argoCDServiceMock.mock.AssertExpectations(t)
|
||||
argoCDServiceMock.Mock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1081,8 +1054,8 @@ cluster:
|
||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
argoCDServiceMock := argoCDServiceMock{mock: &mock.Mock{}}
|
||||
argoCDServiceMock.mock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
||||
argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
|
||||
argoCDServiceMock.Mock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
|
||||
|
||||
var gitGenerator = NewGitGenerator(argoCDServiceMock)
|
||||
@@ -1112,7 +1085,7 @@ cluster:
|
||||
assert.ElementsMatch(t, testCaseCopy.expected, got)
|
||||
}
|
||||
|
||||
argoCDServiceMock.mock.AssertExpectations(t)
|
||||
argoCDServiceMock.Mock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,28 +80,13 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
|
||||
}
|
||||
|
||||
func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, params map[string]interface{}) ([]map[string]interface{}, error) {
|
||||
var matrix *argoprojiov1alpha1.MatrixGenerator
|
||||
if appSetBaseGenerator.Matrix != nil {
|
||||
// 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 err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshall nested matrix generator: %v", err)
|
||||
}
|
||||
if nestedMatrix != nil {
|
||||
matrix = nestedMatrix.ToMatrixGenerator()
|
||||
}
|
||||
matrixGen, err := getMatrixGenerator(appSetBaseGenerator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mergeGenerator *argoprojiov1alpha1.MergeGenerator
|
||||
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.
|
||||
nestedMerge, err := argoprojiov1alpha1.ToNestedMergeGenerator(appSetBaseGenerator.Merge)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshall nested merge generator: %v", err)
|
||||
}
|
||||
if nestedMerge != nil {
|
||||
mergeGenerator = nestedMerge.ToMergeGenerator()
|
||||
}
|
||||
mergeGen, err := getMergeGenerator(appSetBaseGenerator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t, err := Transform(
|
||||
@@ -112,8 +97,8 @@ func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Appli
|
||||
SCMProvider: appSetBaseGenerator.SCMProvider,
|
||||
ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource,
|
||||
PullRequest: appSetBaseGenerator.PullRequest,
|
||||
Matrix: matrix,
|
||||
Merge: mergeGenerator,
|
||||
Matrix: matrixGen,
|
||||
Merge: mergeGen,
|
||||
Selector: appSetBaseGenerator.Selector,
|
||||
},
|
||||
m.supportedGenerators,
|
||||
@@ -143,10 +128,15 @@ func (m *MatrixGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
|
||||
var found bool
|
||||
|
||||
for _, r := range appSetGenerator.Matrix.Generators {
|
||||
matrixGen, _ := getMatrixGenerator(r)
|
||||
mergeGen, _ := getMergeGenerator(r)
|
||||
base := &argoprojiov1alpha1.ApplicationSetGenerator{
|
||||
List: r.List,
|
||||
Clusters: r.Clusters,
|
||||
Git: r.Git,
|
||||
List: r.List,
|
||||
Clusters: r.Clusters,
|
||||
Git: r.Git,
|
||||
PullRequest: r.PullRequest,
|
||||
Matrix: matrixGen,
|
||||
Merge: mergeGen,
|
||||
}
|
||||
generators := GetRelevantGenerators(base, m.supportedGenerators)
|
||||
|
||||
@@ -167,6 +157,17 @@ func (m *MatrixGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
|
||||
|
||||
}
|
||||
|
||||
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 {
|
||||
return &appSetGenerator.Matrix.Template
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
"github.com/stretchr/testify/mock"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
|
||||
testutils "github.com/argoproj/argo-cd/v2/applicationset/utils/test"
|
||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
@@ -399,6 +401,8 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
|
||||
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url"}`)}},
|
||||
}
|
||||
|
||||
pullRequestGenerator := &argoprojiov1alpha1.PullRequestGenerator{}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
|
||||
@@ -431,6 +435,31 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
|
||||
gitGetRequeueAfter: time.Duration(1),
|
||||
expected: time.Duration(1),
|
||||
},
|
||||
{
|
||||
name: "returns the minimal time for pull request",
|
||||
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
|
||||
{
|
||||
Git: gitGenerator,
|
||||
},
|
||||
{
|
||||
PullRequest: pullRequestGenerator,
|
||||
},
|
||||
},
|
||||
gitGetRequeueAfter: time.Duration(15 * time.Second),
|
||||
expected: time.Duration(15 * time.Second),
|
||||
},
|
||||
{
|
||||
name: "returns the default time if no requeueAfterSeconds is provided",
|
||||
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
|
||||
{
|
||||
Git: gitGenerator,
|
||||
},
|
||||
{
|
||||
PullRequest: pullRequestGenerator,
|
||||
},
|
||||
},
|
||||
expected: time.Duration(30 * time.Minute),
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
@@ -441,16 +470,18 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
|
||||
|
||||
for _, g := range testCaseCopy.baseGenerators {
|
||||
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
|
||||
Git: g.Git,
|
||||
List: g.List,
|
||||
Git: g.Git,
|
||||
List: g.List,
|
||||
PullRequest: g.PullRequest,
|
||||
}
|
||||
mock.On("GetRequeueAfter", &gitGeneratorSpec).Return(testCaseCopy.gitGetRequeueAfter, nil)
|
||||
}
|
||||
|
||||
var matrixGenerator = NewMatrixGenerator(
|
||||
map[string]Generator{
|
||||
"Git": mock,
|
||||
"List": &ListGenerator{},
|
||||
"Git": mock,
|
||||
"List": &ListGenerator{},
|
||||
"PullRequest": &PullRequestGenerator{},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -828,3 +859,72 @@ func (g *generatorMock) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Appl
|
||||
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")).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 := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
|
||||
repoServiceMock.Mock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(map[string][]byte{
|
||||
"some/path.json": []byte("test: content"),
|
||||
}, nil)
|
||||
gitGenerator := NewGitGenerator(repoServiceMock)
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
params, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
|
||||
Matrix: matrixGeneratorSpec,
|
||||
}, &argoprojiov1alpha1.ApplicationSet{})
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -137,27 +137,13 @@ func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]interface
|
||||
|
||||
// getParams get the parameters generated by this generator.
|
||||
func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
|
||||
|
||||
var matrix *argoprojiov1alpha1.MatrixGenerator
|
||||
if appSetBaseGenerator.Matrix != nil {
|
||||
nestedMatrix, err := argoprojiov1alpha1.ToNestedMatrixGenerator(appSetBaseGenerator.Matrix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nestedMatrix != nil {
|
||||
matrix = nestedMatrix.ToMatrixGenerator()
|
||||
}
|
||||
matrixGen, err := getMatrixGenerator(appSetBaseGenerator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mergeGenerator *argoprojiov1alpha1.MergeGenerator
|
||||
if appSetBaseGenerator.Merge != nil {
|
||||
nestedMerge, err := argoprojiov1alpha1.ToNestedMergeGenerator(appSetBaseGenerator.Merge)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nestedMerge != nil {
|
||||
mergeGenerator = nestedMerge.ToMergeGenerator()
|
||||
}
|
||||
mergeGen, err := getMergeGenerator(appSetBaseGenerator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t, err := Transform(
|
||||
@@ -168,8 +154,8 @@ func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Applic
|
||||
SCMProvider: appSetBaseGenerator.SCMProvider,
|
||||
ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource,
|
||||
PullRequest: appSetBaseGenerator.PullRequest,
|
||||
Matrix: matrix,
|
||||
Merge: mergeGenerator,
|
||||
Matrix: matrixGen,
|
||||
Merge: mergeGen,
|
||||
Selector: appSetBaseGenerator.Selector,
|
||||
},
|
||||
m.supportedGenerators,
|
||||
@@ -197,10 +183,15 @@ func (m *MergeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.App
|
||||
var found bool
|
||||
|
||||
for _, r := range appSetGenerator.Merge.Generators {
|
||||
matrixGen, _ := getMatrixGenerator(r)
|
||||
mergeGen, _ := getMergeGenerator(r)
|
||||
base := &argoprojiov1alpha1.ApplicationSetGenerator{
|
||||
List: r.List,
|
||||
Clusters: r.Clusters,
|
||||
Git: r.Git,
|
||||
List: r.List,
|
||||
Clusters: r.Clusters,
|
||||
Git: r.Git,
|
||||
PullRequest: r.PullRequest,
|
||||
Matrix: matrixGen,
|
||||
Merge: mergeGen,
|
||||
}
|
||||
generators := GetRelevantGenerators(base, m.supportedGenerators)
|
||||
|
||||
@@ -221,6 +212,17 @@ func (m *MergeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.App
|
||||
|
||||
}
|
||||
|
||||
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, err
|
||||
}
|
||||
return merge.ToMergeGenerator(), nil
|
||||
}
|
||||
|
||||
// GetTemplate gets the Template field for the MergeGenerator.
|
||||
func (m *MergeGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
|
||||
return &appSetGenerator.Merge.Template
|
||||
|
||||
@@ -122,6 +122,15 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error initializing Azure Devops service: %v", err)
|
||||
}
|
||||
} else if providerConfig.Bitbucket != nil {
|
||||
appPassword, err := g.getSecretRef(ctx, providerConfig.Bitbucket.AppPasswordRef, applicationSetInfo.Namespace)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error initializing Bitbucket cloud service: %v", err)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("no SCM provider implementation configured")
|
||||
}
|
||||
|
||||
@@ -85,12 +85,12 @@ func (a *argoCDService) GetDirectories(ctx context.Context, repoURL string, revi
|
||||
|
||||
gitRepoClient, err := git.NewClient(repo.Repo, repo.GetGitCreds(a.storecreds), repo.IsInsecure(), repo.IsLFSEnabled(), repo.Proxy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating a new git client: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = checkoutRepo(gitRepoClient, revision, a.submoduleEnabled)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while checking out repo: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filteredPaths := []string{}
|
||||
@@ -99,7 +99,7 @@ func (a *argoCDService) GetDirectories(ctx context.Context, repoURL string, revi
|
||||
|
||||
if err := filepath.Walk(repoRoot, func(path string, info os.FileInfo, fnErr error) error {
|
||||
if fnErr != nil {
|
||||
return fmt.Errorf("error walking the file tree: %w", fnErr)
|
||||
return fnErr
|
||||
}
|
||||
if !info.IsDir() { // Skip files: directories only
|
||||
return nil
|
||||
@@ -112,7 +112,7 @@ func (a *argoCDService) GetDirectories(ctx context.Context, repoURL string, revi
|
||||
|
||||
relativePath, err := filepath.Rel(repoRoot, path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error constructing relative repo path: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if relativePath == "." { // Exclude '.' from results
|
||||
|
||||
@@ -35,7 +35,7 @@ func NewGiteaProvider(ctx context.Context, owner, token, url string, allBranches
|
||||
}
|
||||
client, err := gitea.NewClient(url, gitea.SetToken(token), gitea.SetHTTPClient(httpClient))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating a new gitea client: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
return &GiteaProvider{
|
||||
client: client,
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"net/http"
|
||||
pathpkg "path"
|
||||
|
||||
gitlab "github.com/xanzy/go-gitlab"
|
||||
@@ -144,7 +145,11 @@ func (g *GitlabProvider) listBranches(_ context.Context, repo *Repository) ([]gi
|
||||
branches := []gitlab.Branch{}
|
||||
// If we don't specifically want to query for all branches, just use the default branch and call it a day.
|
||||
if !g.allBranches {
|
||||
gitlabBranch, _, err := g.client.Branches.GetBranch(repo.RepositoryId, repo.Branch, nil)
|
||||
gitlabBranch, resp, err := g.client.Branches.GetBranch(repo.RepositoryId, repo.Branch, nil)
|
||||
// 404s are not an error here, just a normal false.
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
return []gitlab.Branch{}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -157,6 +162,10 @@ func (g *GitlabProvider) listBranches(_ context.Context, repo *Repository) ([]gi
|
||||
}
|
||||
for {
|
||||
gitlabBranches, resp, err := g.client.Branches.ListBranches(repo.RepositoryId, opt)
|
||||
// 404s are not an error here, just a normal false.
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
return []gitlab.Branch{}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -274,6 +274,8 @@ func gitlabMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
case "/api/v4/projects/27084533/repository/branches/foo":
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
default:
|
||||
_, err := io.WriteString(w, `[]`)
|
||||
if err != nil {
|
||||
@@ -391,3 +393,29 @@ func TestGitlabHasPath(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitlabGetBranches(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gitlabMockHandler(t)(w, r)
|
||||
}))
|
||||
host, _ := NewGitlabProvider(context.Background(), "test-argocd-proton", "", ts.URL, false, true)
|
||||
|
||||
repo := &Repository{
|
||||
RepositoryId: 27084533,
|
||||
Branch: "master",
|
||||
}
|
||||
t.Run("branch exists", func(t *testing.T) {
|
||||
repos, err := host.GetBranches(context.Background(), repo)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, repos[0].Branch, "master")
|
||||
})
|
||||
|
||||
repo2 := &Repository{
|
||||
RepositoryId: 27084533,
|
||||
Branch: "foo",
|
||||
}
|
||||
t.Run("unknown branch", func(t *testing.T) {
|
||||
_, err := host.GetBranches(context.Background(), repo2)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ func CreateOrUpdate(ctx context.Context, c client.Client, obj client.Object, f c
|
||||
// mutate wraps a MutateFn and applies validation to its result
|
||||
func mutate(f controllerutil.MutateFn, key client.ObjectKey, obj client.Object) error {
|
||||
if err := f(); err != nil {
|
||||
return fmt.Errorf("error while wrapping using MutateFn: %w", err)
|
||||
return err
|
||||
}
|
||||
if newKey := client.ObjectKeyFromObject(obj); key != newKey {
|
||||
return fmt.Errorf("MutateFn cannot mutate object name and/or object namespace")
|
||||
|
||||
261
applicationset/utils/selector.go
Normal file
261
applicationset/utils/selector.go
Normal file
@@ -0,0 +1,261 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/klog/v2"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
unaryOperators = []string{
|
||||
string(selection.Exists), string(selection.DoesNotExist),
|
||||
}
|
||||
binaryOperators = []string{
|
||||
string(selection.In), string(selection.NotIn),
|
||||
string(selection.Equals), string(selection.DoubleEquals), string(selection.NotEquals),
|
||||
string(selection.GreaterThan), string(selection.LessThan),
|
||||
}
|
||||
validRequirementOperators = append(binaryOperators, unaryOperators...)
|
||||
)
|
||||
|
||||
// Selector represents a label selector.
|
||||
type Selector interface {
|
||||
// Matches returns true if this selector matches the given set of labels.
|
||||
Matches(labels.Labels) bool
|
||||
|
||||
// Add adds requirements to the Selector
|
||||
Add(r ...Requirement) Selector
|
||||
}
|
||||
|
||||
type internalSelector []Requirement
|
||||
|
||||
// ByKey sorts requirements by key to obtain deterministic parser
|
||||
type ByKey []Requirement
|
||||
|
||||
func (a ByKey) Len() int { return len(a) }
|
||||
|
||||
func (a ByKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
func (a ByKey) Less(i, j int) bool { return a[i].key < a[j].key }
|
||||
|
||||
// Matches for a internalSelector returns true if all
|
||||
// its Requirements match the input Labels. If any
|
||||
// Requirement does not match, false is returned.
|
||||
func (s internalSelector) Matches(l labels.Labels) bool {
|
||||
for ix := range s {
|
||||
if matches := s[ix].Matches(l); !matches {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Add adds requirements to the selector. It copies the current selector returning a new one
|
||||
func (s internalSelector) Add(reqs ...Requirement) Selector {
|
||||
ret := make(internalSelector, 0, len(s)+len(reqs))
|
||||
ret = append(ret, s...)
|
||||
ret = append(ret, reqs...)
|
||||
sort.Sort(ByKey(ret))
|
||||
return ret
|
||||
}
|
||||
|
||||
type nothingSelector struct{}
|
||||
|
||||
func (n nothingSelector) Matches(l labels.Labels) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (n nothingSelector) Add(r ...Requirement) Selector {
|
||||
return n
|
||||
}
|
||||
|
||||
// Nothing returns a selector that matches no labels
|
||||
func nothing() Selector {
|
||||
return nothingSelector{}
|
||||
}
|
||||
|
||||
// Everything returns a selector that matches all labels.
|
||||
func everything() Selector {
|
||||
return internalSelector{}
|
||||
}
|
||||
|
||||
// LabelSelectorAsSelector converts the LabelSelector api type into a struct that implements
|
||||
// labels.Selector
|
||||
// Note: This function should be kept in sync with the selector methods in pkg/labels/selector.go
|
||||
func LabelSelectorAsSelector(ps *v1.LabelSelector) (Selector, error) {
|
||||
if ps == nil {
|
||||
return nothing(), nil
|
||||
}
|
||||
if len(ps.MatchLabels)+len(ps.MatchExpressions) == 0 {
|
||||
return everything(), nil
|
||||
}
|
||||
requirements := make([]Requirement, 0, len(ps.MatchLabels)+len(ps.MatchExpressions))
|
||||
for k, v := range ps.MatchLabels {
|
||||
r, err := newRequirement(k, selection.Equals, []string{v})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requirements = append(requirements, *r)
|
||||
}
|
||||
for _, expr := range ps.MatchExpressions {
|
||||
var op selection.Operator
|
||||
switch expr.Operator {
|
||||
case v1.LabelSelectorOpIn:
|
||||
op = selection.In
|
||||
case v1.LabelSelectorOpNotIn:
|
||||
op = selection.NotIn
|
||||
case v1.LabelSelectorOpExists:
|
||||
op = selection.Exists
|
||||
case v1.LabelSelectorOpDoesNotExist:
|
||||
op = selection.DoesNotExist
|
||||
default:
|
||||
return nil, fmt.Errorf("%q is not a valid pod selector operator", expr.Operator)
|
||||
}
|
||||
r, err := newRequirement(expr.Key, op, append([]string(nil), expr.Values...))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requirements = append(requirements, *r)
|
||||
}
|
||||
selector := newSelector()
|
||||
selector = selector.Add(requirements...)
|
||||
return selector, nil
|
||||
}
|
||||
|
||||
// NewSelector returns a nil selector
|
||||
func newSelector() Selector {
|
||||
return internalSelector(nil)
|
||||
}
|
||||
|
||||
func validateLabelKey(k string, path *field.Path) *field.Error {
|
||||
if errs := validation.IsQualifiedName(k); len(errs) != 0 {
|
||||
return field.Invalid(path, k, strings.Join(errs, "; "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewRequirement is the constructor for a Requirement.
|
||||
// If any of these rules is violated, an error is returned:
|
||||
// (1) The operator can only be In, NotIn, Equals, DoubleEquals, Gt, Lt, NotEquals, Exists, or DoesNotExist.
|
||||
// (2) If the operator is In or NotIn, the values set must be non-empty.
|
||||
// (3) If the operator is Equals, DoubleEquals, or NotEquals, the values set must contain one value.
|
||||
// (4) If the operator is Exists or DoesNotExist, the value set must be empty.
|
||||
// (5) If the operator is Gt or Lt, the values set must contain only one value, which will be interpreted as an integer.
|
||||
// (6) The key is invalid due to its length, or sequence
|
||||
//
|
||||
// of characters. See validateLabelKey for more details.
|
||||
//
|
||||
// The empty string is a valid value in the input values set.
|
||||
// Returned error, if not nil, is guaranteed to be an aggregated field.ErrorList
|
||||
func newRequirement(key string, op selection.Operator, vals []string, opts ...field.PathOption) (*Requirement, error) {
|
||||
var allErrs field.ErrorList
|
||||
path := field.ToPath(opts...)
|
||||
if err := validateLabelKey(key, path.Child("key")); err != nil {
|
||||
allErrs = append(allErrs, err)
|
||||
}
|
||||
|
||||
valuePath := path.Child("values")
|
||||
switch op {
|
||||
case selection.In, selection.NotIn:
|
||||
if len(vals) == 0 {
|
||||
allErrs = append(allErrs, field.Invalid(valuePath, vals, "for 'in', 'notin' operators, values set can't be empty"))
|
||||
}
|
||||
case selection.Equals, selection.DoubleEquals, selection.NotEquals:
|
||||
if len(vals) != 1 {
|
||||
allErrs = append(allErrs, field.Invalid(valuePath, vals, "exact-match compatibility requires one single value"))
|
||||
}
|
||||
case selection.Exists, selection.DoesNotExist:
|
||||
if len(vals) != 0 {
|
||||
allErrs = append(allErrs, field.Invalid(valuePath, vals, "values set must be empty for exists and does not exist"))
|
||||
}
|
||||
case selection.GreaterThan, selection.LessThan:
|
||||
if len(vals) != 1 {
|
||||
allErrs = append(allErrs, field.Invalid(valuePath, vals, "for 'Gt', 'Lt' operators, exactly one value is required"))
|
||||
}
|
||||
for i := range vals {
|
||||
if _, err := strconv.ParseInt(vals[i], 10, 64); err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(valuePath.Index(i), vals[i], "for 'Gt', 'Lt' operators, the value must be an integer"))
|
||||
}
|
||||
}
|
||||
default:
|
||||
allErrs = append(allErrs, field.NotSupported(path.Child("operator"), op, validRequirementOperators))
|
||||
}
|
||||
|
||||
return &Requirement{key: key, operator: op, strValues: vals}, allErrs.ToAggregate()
|
||||
}
|
||||
|
||||
// Requirement contains values, a key, and an operator that relates the key and values.
|
||||
// The zero value of Requirement is invalid.
|
||||
// Requirement implements both set based match and exact match
|
||||
// Requirement should be initialized via NewRequirement constructor for creating a valid Requirement.
|
||||
// +k8s:deepcopy-gen=true
|
||||
type Requirement struct {
|
||||
key string
|
||||
operator selection.Operator
|
||||
// In the majority of cases we have at most one value here.
|
||||
// It is generally faster to operate on a single-element slice
|
||||
// than on a single-element map, so we have a slice here.
|
||||
strValues []string
|
||||
}
|
||||
|
||||
func (r *Requirement) hasValue(value string) bool {
|
||||
for i := range r.strValues {
|
||||
if r.strValues[i] == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *Requirement) Matches(ls labels.Labels) bool {
|
||||
switch r.operator {
|
||||
case selection.In, selection.Equals, selection.DoubleEquals:
|
||||
if !ls.Has(r.key) {
|
||||
return false
|
||||
}
|
||||
return r.hasValue(ls.Get(r.key))
|
||||
case selection.NotIn, selection.NotEquals:
|
||||
if !ls.Has(r.key) {
|
||||
return true
|
||||
}
|
||||
return !r.hasValue(ls.Get(r.key))
|
||||
case selection.Exists:
|
||||
return ls.Has(r.key)
|
||||
case selection.DoesNotExist:
|
||||
return !ls.Has(r.key)
|
||||
case selection.GreaterThan, selection.LessThan:
|
||||
if !ls.Has(r.key) {
|
||||
return false
|
||||
}
|
||||
lsValue, err := strconv.ParseInt(ls.Get(r.key), 10, 64)
|
||||
if err != nil {
|
||||
klog.V(10).Infof("ParseInt failed for value %+v in label %+v, %+v", ls.Get(r.key), ls, err)
|
||||
return false
|
||||
}
|
||||
|
||||
// There should be only one strValue in r.strValues, and can be converted to an integer.
|
||||
if len(r.strValues) != 1 {
|
||||
klog.V(10).Infof("Invalid values count %+v of requirement %#v, for 'Gt', 'Lt' operators, exactly one value is required", len(r.strValues), r)
|
||||
return false
|
||||
}
|
||||
|
||||
var rValue int64
|
||||
for i := range r.strValues {
|
||||
rValue, err = strconv.ParseInt(r.strValues[i], 10, 64)
|
||||
if err != nil {
|
||||
klog.V(10).Infof("ParseInt failed for value %+v in requirement %#v, for 'Gt', 'Lt' operators, the value must be an integer", r.strValues[i], r)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return (r.operator == selection.GreaterThan && lsValue > rValue) || (r.operator == selection.LessThan && lsValue < rValue)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
34
applicationset/utils/test/testutils.go
Normal file
34
applicationset/utils/test/testutils.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type ArgoCDServiceMock struct {
|
||||
Mock *mock.Mock
|
||||
}
|
||||
|
||||
func (a ArgoCDServiceMock) GetApps(ctx context.Context, repoURL string, revision string) ([]string, error) {
|
||||
args := a.Mock.Called(ctx, repoURL, revision)
|
||||
|
||||
return args.Get(0).([]string), args.Error(1)
|
||||
}
|
||||
|
||||
func (a ArgoCDServiceMock) GetFiles(ctx context.Context, repoURL string, revision string, pattern string) (map[string][]byte, error) {
|
||||
args := a.Mock.Called(ctx, repoURL, revision, pattern)
|
||||
|
||||
return args.Get(0).(map[string][]byte), args.Error(1)
|
||||
}
|
||||
|
||||
func (a ArgoCDServiceMock) GetFileContent(ctx context.Context, repoURL string, revision string, path string) ([]byte, error) {
|
||||
args := a.Mock.Called(ctx, repoURL, revision, path)
|
||||
|
||||
return args.Get(0).([]byte), args.Error(1)
|
||||
}
|
||||
|
||||
func (a ArgoCDServiceMock) GetDirectories(ctx context.Context, repoURL string, revision string) ([]string, error) {
|
||||
args := a.Mock.Called(ctx, repoURL, revision)
|
||||
return args.Get(0).([]string), args.Error(1)
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"text/template"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/Masterminds/sprig"
|
||||
"github.com/valyala/fasttemplate"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -174,7 +174,7 @@ func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[stri
|
||||
|
||||
func (r *Render) RenderTemplateParams(tmpl *argoappsv1.Application, syncPolicy *argoappsv1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool) (*argoappsv1.Application, error) {
|
||||
if tmpl == nil {
|
||||
return nil, fmt.Errorf("application template is empty ")
|
||||
return nil, fmt.Errorf("application template is empty")
|
||||
}
|
||||
|
||||
if len(params) == 0 {
|
||||
@@ -204,6 +204,27 @@ func (r *Render) RenderTemplateParams(tmpl *argoappsv1.Application, syncPolicy *
|
||||
return replacedTmpl, nil
|
||||
}
|
||||
|
||||
func (r *Render) RenderGeneratorParams(gen *argoappsv1.ApplicationSetGenerator, params map[string]interface{}, useGoTemplate bool) (*argoappsv1.ApplicationSetGenerator, error) {
|
||||
if gen == nil {
|
||||
return nil, fmt.Errorf("generator is empty")
|
||||
}
|
||||
|
||||
if len(params) == 0 {
|
||||
return gen, nil
|
||||
}
|
||||
|
||||
original := reflect.ValueOf(gen)
|
||||
copy := reflect.New(original.Type()).Elem()
|
||||
|
||||
if err := r.deeplyReplace(copy, original, params, useGoTemplate); err != nil {
|
||||
return nil, fmt.Errorf("failed to replace parameters in generator: %w", err)
|
||||
}
|
||||
|
||||
replacedGen := copy.Interface().(*argoappsv1.ApplicationSetGenerator)
|
||||
|
||||
return replacedGen, nil
|
||||
}
|
||||
|
||||
var isTemplatedRegex = regexp.MustCompile(".*{{.*}}.*")
|
||||
|
||||
// Replace executes basic string substitution of a template with replacement values.
|
||||
@@ -227,7 +248,10 @@ func (r *Render) Replace(tmpl string, replaceMap map[string]interface{}, useGoTe
|
||||
return tmpl, nil
|
||||
}
|
||||
|
||||
fstTmpl := fasttemplate.New(tmpl, "{{", "}}")
|
||||
fstTmpl, err := fasttemplate.NewTemplate(tmpl, "{{", "}}")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid template: %w", err)
|
||||
}
|
||||
replacedTmpl := fstTmpl.ExecuteFuncString(func(w io.Writer, tag string) (int, error) {
|
||||
trimmedTag := strings.TrimSpace(tag)
|
||||
replacement, ok := replaceMap[trimmedTag].(string)
|
||||
|
||||
@@ -464,6 +464,14 @@ func TestRenderTemplateParamsGoTemplate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Render_Replace_no_panic_on_missing_closing_brace(t *testing.T) {
|
||||
r := &Render{}
|
||||
assert.NotPanics(t, func() {
|
||||
_, err := r.Replace("{{properly.closed}} {{improperly.closed}", nil, false)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRenderTemplateKeys(t *testing.T) {
|
||||
t.Run("fasttemplate", func(t *testing.T) {
|
||||
application := &argoappsv1.Application{
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEesHEB7vX5Y2RxXypjMy1nI1z7iRG
|
||||
JI9/gt/sYqzpsa65aaNP4npM43DDxoIy/MQBo9s/mxGxmA+8UXeDpVC9vw==
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -271,6 +271,16 @@
|
||||
"description": "the application's namespace.",
|
||||
"name": "appNamespace",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "multi",
|
||||
"description": "the project names to restrict returned list applications (legacy name for backwards-compatibility).",
|
||||
"name": "project",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -585,6 +595,16 @@
|
||||
"description": "the application's namespace.",
|
||||
"name": "appNamespace",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "multi",
|
||||
"description": "the project names to restrict returned list applications (legacy name for backwards-compatibility).",
|
||||
"name": "project",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -3507,6 +3527,16 @@
|
||||
"description": "the application's namespace.",
|
||||
"name": "appNamespace",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "multi",
|
||||
"description": "the project names to restrict returned list applications (legacy name for backwards-compatibility).",
|
||||
"name": "project",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -4508,65 +4538,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repositoryParameterAnnouncement": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"array": {
|
||||
"description": "array is the default value of the parameter if the parameter is an array.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"collectionType": {
|
||||
"description": "collectionType is the type of value this parameter holds - either a single value (a string) or a collection\n(array or map). If collectionType is set, only the field with that type will be used. If collectionType is not\nset, `string` is the default. If collectionType is set to an invalid value, a validation error is thrown.",
|
||||
"type": "string"
|
||||
},
|
||||
"itemType": {
|
||||
"description": "itemType determines the primitive data type represented by the parameter. Parameters are always encoded as\nstrings, but this field lets them be interpreted as other primitive types.",
|
||||
"type": "string"
|
||||
},
|
||||
"map": {
|
||||
"description": "map is the default value of the parameter if the parameter is a map.",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"description": "name is the name identifying a parameter.",
|
||||
"type": "string"
|
||||
},
|
||||
"required": {
|
||||
"description": "required defines if this given parameter is mandatory.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"string": {
|
||||
"description": "string is the default value of the parameter if the parameter is a string.",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "title is a human-readable text of the parameter name.",
|
||||
"type": "string"
|
||||
},
|
||||
"tooltip": {
|
||||
"description": "tooltip is a human-readable description of the parameter.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repositoryPluginAppSpec": {
|
||||
"type": "object",
|
||||
"title": "PluginAppSpec contains details about a plugin-type Application",
|
||||
"properties": {
|
||||
"parametersAnnouncement": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/repositoryParameterAnnouncement"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"repositoryRefs": {
|
||||
"type": "object",
|
||||
"title": "A subset of the repository's named refs",
|
||||
@@ -4613,9 +4584,6 @@
|
||||
"kustomize": {
|
||||
"$ref": "#/definitions/repositoryKustomizeAppSpec"
|
||||
},
|
||||
"plugin": {
|
||||
"$ref": "#/definitions/repositoryPluginAppSpec"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -5837,39 +5805,6 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"parameters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/v1alpha1ApplicationSourcePluginParameter"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1alpha1ApplicationSourcePluginParameter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"array": {
|
||||
"description": "Array is the value of an array type parameter.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"map": {
|
||||
"description": "Map is the value of a map type parameter.",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the name identifying a parameter.",
|
||||
"type": "string"
|
||||
},
|
||||
"string": {
|
||||
"description": "String_ is the value of a string type parameter.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -6642,23 +6577,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1alpha1ManagedNamespaceMetadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1alpha1MatrixGenerator": {
|
||||
"description": "MatrixGenerator generates the cartesian product of two sets of parameters. The parameters are defined by two nested\ngenerators.",
|
||||
"type": "object",
|
||||
@@ -7597,8 +7515,8 @@
|
||||
"$ref": "#/definitions/v1Time"
|
||||
},
|
||||
"message": {
|
||||
"description": "Message contains the message associated with the revision, most likely the commit message.",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"title": "Message contains the message associated with the revision, most likely the commit message.\nThe message is truncated to the first newline or 64 characters (which ever comes first)"
|
||||
},
|
||||
"signatureInfo": {
|
||||
"description": "SignatureInfo contains a hint on the signer if the revision was signed with GPG, and signature verification is enabled.",
|
||||
@@ -7942,9 +7860,6 @@
|
||||
"automated": {
|
||||
"$ref": "#/definitions/v1alpha1SyncPolicyAutomated"
|
||||
},
|
||||
"managedNamespaceMetadata": {
|
||||
"$ref": "#/definitions/v1alpha1ManagedNamespaceMetadata"
|
||||
},
|
||||
"retry": {
|
||||
"$ref": "#/definitions/v1alpha1RetryStrategy"
|
||||
},
|
||||
|
||||
@@ -39,7 +39,7 @@ import (
|
||||
argosettings "github.com/argoproj/argo-cd/v2/util/settings"
|
||||
)
|
||||
|
||||
// TODO: load this using Cobra.
|
||||
// TODO: load this using Cobra. https://github.com/argoproj/argo-cd/issues/10157
|
||||
func getSubmoduleEnabled() bool {
|
||||
return env.ParseBoolFromEnv(common.EnvGitSubmoduleEnabled, true)
|
||||
}
|
||||
@@ -195,16 +195,16 @@ func NewCommand() *cobra.Command {
|
||||
command.Flags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
|
||||
command.Flags().StringVar(&probeBindAddr, "probe-addr", ":8081", "The address the probe endpoint binds to.")
|
||||
command.Flags().StringVar(&webhookAddr, "webhook-addr", ":7000", "The address the webhook endpoint binds to.")
|
||||
command.Flags().BoolVar(&enableLeaderElection, "enable-leader-election", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_LEADER_ELECTION", false),
|
||||
command.Flags().BoolVar(&enableLeaderElection, "enable-leader-election", false,
|
||||
"Enable leader election for controller manager. "+
|
||||
"Enabling this will ensure there is only one active controller manager.")
|
||||
command.Flags().StringVar(&namespace, "namespace", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACE", ""), "Argo CD repo namespace (default: argocd)")
|
||||
command.Flags().StringVar(&argocdRepoServer, "argocd-repo-server", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER", common.DefaultRepoServerAddr), "Argo CD repo server address")
|
||||
command.Flags().StringVar(&policy, "policy", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_POLICY", "sync"), "Modify how application is synced between the generator and the cluster. Default is 'sync' (create & update & delete), options: 'create-only', 'create-update' (no deletion)")
|
||||
command.Flags().BoolVar(&debugLog, "debug", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_DEBUG", false), "Print debug logs. Takes precedence over loglevel")
|
||||
command.Flags().StringVar(&cmdutil.LogFormat, "logformat", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_LOGFORMAT", "text"), "Set the logging format. One of: text|json")
|
||||
command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_LOGLEVEL", "info"), "Set the logging level. One of: debug|info|warn|error")
|
||||
command.Flags().BoolVar(&dryRun, "dry-run", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_DRY_RUN", false), "Enable dry run mode")
|
||||
command.Flags().StringVar(&namespace, "namespace", "", "Argo CD repo namespace (default: argocd)")
|
||||
command.Flags().StringVar(&argocdRepoServer, "argocd-repo-server", "argocd-repo-server:8081", "Argo CD repo server address")
|
||||
command.Flags().StringVar(&policy, "policy", "sync", "Modify how application is synced between the generator and the cluster. Default is 'sync' (create & update & delete), options: 'create-only', 'create-update' (no deletion)")
|
||||
command.Flags().BoolVar(&debugLog, "debug", false, "Print debug logs. Takes precedence over loglevel")
|
||||
command.Flags().StringVar(&cmdutil.LogFormat, "logformat", "text", "Set the logging format. One of: text|json")
|
||||
command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
|
||||
command.Flags().BoolVar(&dryRun, "dry-run", false, "Enable dry run mode")
|
||||
return &command
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ func NewAccountCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
command.AddCommand(NewAccountGenerateTokenCommand(clientOpts))
|
||||
command.AddCommand(NewAccountGetCommand(clientOpts))
|
||||
command.AddCommand(NewAccountDeleteTokenCommand(clientOpts))
|
||||
command.AddCommand(NewBcryptCmd())
|
||||
return command
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ func NewAdminCommand() *cobra.Command {
|
||||
command.AddCommand(NewExportCommand())
|
||||
command.AddCommand(NewDashboardCommand())
|
||||
command.AddCommand(NewNotificationsCommand())
|
||||
command.AddCommand(NewInitialPasswordCommand())
|
||||
|
||||
command.Flags().StringVar(&cmdutil.LogFormat, "logformat", "text", "Set the logging format. One of: text|json")
|
||||
command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
|
||||
|
||||
@@ -292,11 +292,11 @@ func saveToFile(err error, outputFormat string, result reconcileResults, outputP
|
||||
switch outputFormat {
|
||||
case "yaml":
|
||||
if data, err = yaml.Marshal(result); err != nil {
|
||||
return fmt.Errorf("error marshalling yaml: %w", err)
|
||||
return err
|
||||
}
|
||||
case "json":
|
||||
if data, err = json.Marshal(result); err != nil {
|
||||
return fmt.Errorf("error marshalling json: %w", err)
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("format %s is not supported", outputFormat)
|
||||
|
||||
@@ -221,11 +221,11 @@ func printStatsSummary(clusters []ClusterWithInfo) {
|
||||
func runClusterNamespacesCommand(ctx context.Context, clientConfig clientcmd.ClientConfig, action func(appClient *versioned.Clientset, argoDB db.ArgoDB, clusters map[string][]string) error) error {
|
||||
clientCfg, err := clientConfig.ClientConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while creating client config: %w", err)
|
||||
return err
|
||||
}
|
||||
namespace, _, err := clientConfig.Namespace()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while getting namespace from client config: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
kubeClient := kubernetes.NewForConfigOrDie(clientCfg)
|
||||
@@ -235,16 +235,17 @@ func runClusterNamespacesCommand(ctx context.Context, clientConfig clientcmd.Cli
|
||||
argoDB := db.NewDB(namespace, settingsMgr, kubeClient)
|
||||
clustersList, err := argoDB.ListClusters(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing clusters: %w", err)
|
||||
return err
|
||||
}
|
||||
appItems, err := appClient.ArgoprojV1alpha1().Applications(namespace).List(ctx, v1.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing application: %w", err)
|
||||
return err
|
||||
}
|
||||
apps := appItems.Items
|
||||
for i, app := range apps {
|
||||
if err := argo.ValidateDestination(ctx, &app.Spec.Destination, argoDB); err != nil {
|
||||
return fmt.Errorf("error validating application destination: %w", err)
|
||||
err := argo.ValidateDestination(ctx, &app.Spec.Destination, argoDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apps[i] = app
|
||||
}
|
||||
@@ -348,14 +349,15 @@ func NewClusterEnableNamespacedMode() *cobra.Command {
|
||||
|
||||
cluster, err := argoDB.GetCluster(ctx, server)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting cluster from server: %w", err)
|
||||
return err
|
||||
}
|
||||
cluster.Namespaces = namespaces
|
||||
cluster.ClusterResources = clusterResources
|
||||
fmt.Printf("Setting cluster %s namespaces to %v...", server, namespaces)
|
||||
if !dryRun {
|
||||
if _, err = argoDB.UpdateCluster(ctx, cluster); err != nil {
|
||||
return fmt.Errorf("error updating cluster: %w", err)
|
||||
_, err = argoDB.UpdateCluster(ctx, cluster)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("done")
|
||||
} else {
|
||||
@@ -403,7 +405,7 @@ func NewClusterDisableNamespacedMode() *cobra.Command {
|
||||
|
||||
cluster, err := argoDB.GetCluster(ctx, server)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting cluster from server: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(cluster.Namespaces) == 0 {
|
||||
@@ -413,8 +415,9 @@ func NewClusterDisableNamespacedMode() *cobra.Command {
|
||||
cluster.Namespaces = nil
|
||||
fmt.Printf("Disabling namespaced mode for cluster %s...", server)
|
||||
if !dryRun {
|
||||
if _, err = argoDB.UpdateCluster(ctx, cluster); err != nil {
|
||||
return fmt.Errorf("error updating cluster: %w", err)
|
||||
_, err = argoDB.UpdateCluster(ctx, cluster)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("done")
|
||||
} else {
|
||||
|
||||
@@ -9,13 +9,16 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/initialize"
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/util/cache"
|
||||
"github.com/argoproj/argo-cd/v2/util/env"
|
||||
"github.com/argoproj/argo-cd/v2/util/errors"
|
||||
)
|
||||
|
||||
func NewDashboardCommand() *cobra.Command {
|
||||
var (
|
||||
port int
|
||||
address string
|
||||
port int
|
||||
address string
|
||||
compressionStr string
|
||||
)
|
||||
cmd := &cobra.Command{
|
||||
Use: "dashboard",
|
||||
@@ -23,7 +26,9 @@ func NewDashboardCommand() *cobra.Command {
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := cmd.Context()
|
||||
|
||||
errors.CheckError(headless.StartLocalServer(ctx, &argocdclient.ClientOptions{Core: true}, initialize.RetrieveContextIfChanged(cmd.Flag("context")), &port, &address))
|
||||
compression, err := cache.CompressionTypeFromString(compressionStr)
|
||||
errors.CheckError(err)
|
||||
errors.CheckError(headless.StartLocalServer(ctx, &argocdclient.ClientOptions{Core: true}, initialize.RetrieveContextIfChanged(cmd.Flag("context")), &port, &address, compression))
|
||||
println(fmt.Sprintf("Argo CD UI is available at http://%s:%d", address, port))
|
||||
<-ctx.Done()
|
||||
},
|
||||
@@ -31,5 +36,6 @@ func NewDashboardCommand() *cobra.Command {
|
||||
initialize.InitCommand(cmd)
|
||||
cmd.Flags().IntVar(&port, "port", common.DefaultPortAPIServer, "Listen on given port")
|
||||
cmd.Flags().StringVar(&address, "address", common.DefaultAddressAPIServer, "Listen on given address")
|
||||
cmd.Flags().StringVar(&compressionStr, "redis-compress", env.StringFromEnv("REDIS_COMPRESSION", string(cache.RedisCompressionNone)), "Enable this if the application controller is configured with redis compression enabled. (possible values: none, gzip)")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func PrintResources(output string, out io.Writer, resources ...interface{}) erro
|
||||
}
|
||||
filteredResource, err := omitFields(resource)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error omitting filtered fields from the resource: %w", err)
|
||||
return err
|
||||
}
|
||||
resources[i] = filteredResource
|
||||
}
|
||||
@@ -56,14 +56,14 @@ func PrintResources(output string, out io.Writer, resources ...interface{}) erro
|
||||
case "json":
|
||||
jsonBytes, err := json.MarshalIndent(obj, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling json: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(out, string(jsonBytes))
|
||||
case "yaml":
|
||||
yamlBytes, err := yaml.Marshal(obj)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling yaml: %w", err)
|
||||
return err
|
||||
}
|
||||
// marshaled YAML already ends with the new line character
|
||||
_, _ = fmt.Fprint(out, string(yamlBytes))
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/util/cli"
|
||||
"github.com/argoproj/argo-cd/v2/util/errors"
|
||||
)
|
||||
|
||||
const initialPasswordSecretName = "argocd-initial-admin-secret"
|
||||
|
||||
// NewInitialPasswordCommand defines a new command to retrieve Argo CD initial password.
|
||||
func NewInitialPasswordCommand() *cobra.Command {
|
||||
var (
|
||||
clientConfig clientcmd.ClientConfig
|
||||
)
|
||||
var command = cobra.Command{
|
||||
Use: "initial-password",
|
||||
Short: "Prints initial password to log in to Argo CD for the first time",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
|
||||
config, err := clientConfig.ClientConfig()
|
||||
errors.CheckError(err)
|
||||
namespace, _, err := clientConfig.Namespace()
|
||||
errors.CheckError(err)
|
||||
|
||||
kubeClientset := kubernetes.NewForConfigOrDie(config)
|
||||
secret, err := kubeClientset.CoreV1().Secrets(namespace).Get(context.Background(), initialPasswordSecretName, v1.GetOptions{})
|
||||
errors.CheckError(err)
|
||||
|
||||
if initialPass, ok := secret.Data["password"]; ok {
|
||||
fmt.Println(string(initialPass))
|
||||
fmt.Println("\n This password must be only used for first time login. We strongly recommend you update the password using `argocd account update-password`.")
|
||||
}
|
||||
},
|
||||
}
|
||||
clientConfig = cli.AddKubectlFlagsToCmd(&command)
|
||||
|
||||
return &command
|
||||
}
|
||||
@@ -106,13 +106,13 @@ func saveProject(ctx context.Context, updated v1alpha1.AppProject, orig v1alpha1
|
||||
errors.CheckError(err)
|
||||
live, err := kube.ToUnstructured(&orig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error converting project to unstructured: %w", err)
|
||||
return err
|
||||
}
|
||||
_ = cli.PrintDiff(updated.Name, target, live)
|
||||
if !dryRun {
|
||||
_, err = projectsIf.Update(ctx, &updated, v1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while updating project: %w", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -188,7 +188,7 @@ func NewUpdatePolicyRuleCommand() *cobra.Command {
|
||||
func updateProjects(ctx context.Context, projIf appclient.AppProjectInterface, projectGlob string, rolePattern string, action string, modification func(string, string) string, dryRun bool) error {
|
||||
projects, err := projIf.List(ctx, v1.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing the projects: %w", err)
|
||||
return err
|
||||
}
|
||||
for _, proj := range projects.Items {
|
||||
if !globMatch(projectGlob, proj.Name) {
|
||||
@@ -225,7 +225,7 @@ func updateProjects(ctx context.Context, projIf appclient.AppProjectInterface, p
|
||||
if updated {
|
||||
err = saveProject(ctx, proj, *origProj, projIf, dryRun)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error saving the project: %w", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ var validatorsByGroup = map[string]settingValidator{
|
||||
}
|
||||
ssoProvider = "Dex"
|
||||
} else if general.OIDCConfigRAW != "" {
|
||||
if _, err := settings.UnmarshalOIDCConfig(general.OIDCConfigRAW); err != nil {
|
||||
if err := settings.ValidateOIDCConfig(general.OIDCConfigRAW); err != nil {
|
||||
return "", fmt.Errorf("invalid oidc.config: %v", err)
|
||||
}
|
||||
ssoProvider = "OIDC"
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -373,6 +374,9 @@ func resolveRBACResourceName(name string) string {
|
||||
|
||||
// isValidRBACAction checks whether a given action is a valid RBAC action
|
||||
func isValidRBACAction(action string) bool {
|
||||
if strings.HasPrefix(action, rbacpolicy.ActionAction+"/") {
|
||||
return true
|
||||
}
|
||||
_, ok := validRBACActions[action]
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -27,6 +27,11 @@ func Test_isValidRBACAction(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_isValidRBACAction_ActionAction(t *testing.T) {
|
||||
ok := isValidRBACAction("action/apps/Deployment/restart")
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func Test_isValidRBACResource(t *testing.T) {
|
||||
for k := range validRBACResources {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
|
||||
@@ -169,7 +169,9 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
|
||||
|
||||
// Get app before creating to see if it is being updated or no change
|
||||
existing, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{Name: &app.Name})
|
||||
if grpc.UnwrapGRPCStatus(err).Code() != codes.NotFound {
|
||||
unwrappedError := grpc.UnwrapGRPCStatus(err).Code()
|
||||
// As part of the fix for CVE-2022-41354, the API will return Permission Denied when an app does not exist.
|
||||
if unwrappedError != codes.NotFound && unwrappedError != codes.PermissionDenied {
|
||||
errors.CheckError(err)
|
||||
}
|
||||
|
||||
@@ -964,7 +966,7 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
|
||||
diffOption.serversideRes = res
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Warning: local diff without --server-side-generate is deprecated and does not work with plugins. Server-side generation will be the default in v2.6.")
|
||||
fmt.Fprintf(os.Stderr, "Warning: local diff without --server-side-generate is deprecated and does not work with plugins. Server-side generation will be the default in v2.7.")
|
||||
conn, clusterIf := clientset.NewClusterClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
cluster, err := clusterIf.Get(ctx, &clusterpkg.ClusterQuery{Name: app.Spec.Destination.Name, Server: app.Spec.Destination.Server})
|
||||
@@ -1018,7 +1020,7 @@ func findandPrintDiff(ctx context.Context, app *argoappv1.Application, resources
|
||||
unstructureds = append(unstructureds, obj)
|
||||
}
|
||||
groupedObjs := groupObjsByKey(unstructureds, liveObjs, app.Spec.Destination.Namespace)
|
||||
items = groupObjsForDiff(resources, groupedObjs, items, argoSettings, app.Name)
|
||||
items = groupObjsForDiff(resources, groupedObjs, items, argoSettings, app.InstanceName(argoSettings.ControllerNamespace))
|
||||
} else if diffOptions.serversideRes != nil {
|
||||
var unstructureds []*unstructured.Unstructured
|
||||
for _, mfst := range diffOptions.serversideRes.Manifests {
|
||||
@@ -1027,7 +1029,7 @@ func findandPrintDiff(ctx context.Context, app *argoappv1.Application, resources
|
||||
unstructureds = append(unstructureds, obj)
|
||||
}
|
||||
groupedObjs := groupObjsByKey(unstructureds, liveObjs, app.Spec.Destination.Namespace)
|
||||
items = groupObjsForDiff(resources, groupedObjs, items, argoSettings, app.Name)
|
||||
items = groupObjsForDiff(resources, groupedObjs, items, argoSettings, app.InstanceName(argoSettings.ControllerNamespace))
|
||||
} else {
|
||||
for i := range resources.Items {
|
||||
res := resources.Items[i]
|
||||
@@ -2427,12 +2429,12 @@ func NewApplicationEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
cli.InteractiveEdit(fmt.Sprintf("%s-*-edit.yaml", appName), appData, func(input []byte) error {
|
||||
input, err = yaml.YAMLToJSON(input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error converting YAML to JSON: %w", err)
|
||||
return err
|
||||
}
|
||||
updatedSpec := argoappv1.ApplicationSpec{}
|
||||
err = json.Unmarshal(input, &updatedSpec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error unmarshaling input into application spec: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
var appOpts cmdutil.AppOptions
|
||||
@@ -2444,9 +2446,9 @@ func NewApplicationEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
AppNamespace: &appNs,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update application spec: %w", err)
|
||||
return fmt.Errorf("Failed to update application spec:\n%v", err)
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -342,16 +342,19 @@ func printAppSetSummaryTable(appSet *arogappsetv1.ApplicationSet) {
|
||||
fmt.Printf(printOpFmtStr, "Path:", appSet.Spec.Template.Spec.Source.Path)
|
||||
printAppSourceDetails(&appSet.Spec.Template.Spec.Source)
|
||||
|
||||
var syncPolicy string
|
||||
if appSet.Spec.SyncPolicy != nil && appSet.Spec.Template.Spec.SyncPolicy.Automated != nil {
|
||||
syncPolicy = "Automated"
|
||||
if appSet.Spec.Template.Spec.SyncPolicy.Automated.Prune {
|
||||
syncPolicy += " (Prune)"
|
||||
var (
|
||||
syncPolicyStr string
|
||||
syncPolicy = appSet.Spec.Template.Spec.SyncPolicy
|
||||
)
|
||||
if syncPolicy != nil && syncPolicy.Automated != nil {
|
||||
syncPolicyStr = "Automated"
|
||||
if syncPolicy.Automated.Prune {
|
||||
syncPolicyStr += " (Prune)"
|
||||
}
|
||||
} else {
|
||||
syncPolicy = "<none>"
|
||||
syncPolicyStr = "<none>"
|
||||
}
|
||||
fmt.Printf(printOpFmtStr, "SyncPolicy:", syncPolicy)
|
||||
fmt.Printf(printOpFmtStr, "SyncPolicy:", syncPolicyStr)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
@@ -68,3 +70,124 @@ func TestPrintApplicationSetTable(t *testing.T) {
|
||||
expectation := "NAME NAMESPACE PROJECT SYNCPOLICY CONDITIONS\napp-name default nil [{ResourcesUpToDate <nil> True }]\napp-name default nil [{ResourcesUpToDate <nil> True }]\n"
|
||||
assert.Equal(t, expectation, output)
|
||||
}
|
||||
|
||||
func TestPrintAppSetSummaryTable(t *testing.T) {
|
||||
baseAppSet := &arogappsetv1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-name",
|
||||
},
|
||||
Spec: arogappsetv1.ApplicationSetSpec{
|
||||
Generators: []arogappsetv1.ApplicationSetGenerator{
|
||||
arogappsetv1.ApplicationSetGenerator{
|
||||
Git: &arogappsetv1.GitGenerator{
|
||||
RepoURL: "https://github.com/argoproj/argo-cd.git",
|
||||
Revision: "head",
|
||||
Directories: []arogappsetv1.GitDirectoryGeneratorItem{
|
||||
arogappsetv1.GitDirectoryGeneratorItem{
|
||||
Path: "applicationset/examples/git-generator-directory/cluster-addons/*",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Template: arogappsetv1.ApplicationSetTemplate{
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Project: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: arogappsetv1.ApplicationSetStatus{
|
||||
Conditions: []arogappsetv1.ApplicationSetCondition{
|
||||
arogappsetv1.ApplicationSetCondition{
|
||||
Status: v1alpha1.ApplicationSetConditionStatusTrue,
|
||||
Type: arogappsetv1.ApplicationSetConditionResourcesUpToDate,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
appsetSpecSyncPolicy := baseAppSet.DeepCopy()
|
||||
appsetSpecSyncPolicy.Spec.SyncPolicy = &arogappsetv1.ApplicationSetSyncPolicy{
|
||||
PreserveResourcesOnDeletion: true,
|
||||
}
|
||||
|
||||
appSetTemplateSpecSyncPolicy := baseAppSet.DeepCopy()
|
||||
appSetTemplateSpecSyncPolicy.Spec.Template.Spec.SyncPolicy = &arogappsetv1.SyncPolicy{
|
||||
Automated: &arogappsetv1.SyncPolicyAutomated{
|
||||
SelfHeal: true,
|
||||
},
|
||||
}
|
||||
|
||||
appSetBothSyncPolicies := baseAppSet.DeepCopy()
|
||||
appSetBothSyncPolicies.Spec.SyncPolicy = &arogappsetv1.ApplicationSetSyncPolicy{
|
||||
PreserveResourcesOnDeletion: true,
|
||||
}
|
||||
appSetBothSyncPolicies.Spec.Template.Spec.SyncPolicy = &arogappsetv1.SyncPolicy{
|
||||
Automated: &arogappsetv1.SyncPolicyAutomated{
|
||||
SelfHeal: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
appSet *arogappsetv1.ApplicationSet
|
||||
expectedOutput string
|
||||
}{
|
||||
{
|
||||
name: "appset with only spec.syncPolicy set",
|
||||
appSet: appsetSpecSyncPolicy,
|
||||
expectedOutput: `Name: app-name
|
||||
Project: default
|
||||
Server:
|
||||
Namespace:
|
||||
Repo:
|
||||
Target:
|
||||
Path:
|
||||
SyncPolicy: <none>
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "appset with only spec.template.spec.syncPolicy set",
|
||||
appSet: appSetTemplateSpecSyncPolicy,
|
||||
expectedOutput: `Name: app-name
|
||||
Project: default
|
||||
Server:
|
||||
Namespace:
|
||||
Repo:
|
||||
Target:
|
||||
Path:
|
||||
SyncPolicy: Automated
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "appset with both spec.SyncPolicy and spec.template.spec.syncPolicy set",
|
||||
appSet: appSetBothSyncPolicies,
|
||||
expectedOutput: `Name: app-name
|
||||
Project: default
|
||||
Server:
|
||||
Namespace:
|
||||
Repo:
|
||||
Target:
|
||||
Path:
|
||||
SyncPolicy: Automated
|
||||
`,
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
oldStdout := os.Stdout
|
||||
defer func() {
|
||||
os.Stdout = oldStdout
|
||||
}()
|
||||
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
printAppSetSummaryTable(tt.appSet)
|
||||
w.Close()
|
||||
|
||||
out, err := ioutil.ReadAll(r)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedOutput, string(out))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// bcryptCmd represents the bcrypt command
|
||||
func NewBcryptCmd() *cobra.Command {
|
||||
var (
|
||||
password string
|
||||
)
|
||||
var bcryptCmd = &cobra.Command{
|
||||
Use: "bcrypt",
|
||||
Short: "Generate bcrypt hash for the admin password",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
bytePassword := []byte(password)
|
||||
// Hashing the password
|
||||
hash, err := bcrypt.GenerateFromPassword(bytePassword, bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to genarate bcrypt hash: %v", err)
|
||||
}
|
||||
fmt.Fprint(cmd.OutOrStdout(), string(hash))
|
||||
},
|
||||
}
|
||||
|
||||
bcryptCmd.Flags().StringVar(&password, "password", "", "Password for which bcrypt hash is generated")
|
||||
err := bcryptCmd.MarkFlagRequired("password")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return bcryptCmd
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func TestGeneratePassword(t *testing.T) {
|
||||
bcryptCmd := NewBcryptCmd()
|
||||
bcryptCmd.SetArgs([]string{"--password", "abc"})
|
||||
output := new(bytes.Buffer)
|
||||
bcryptCmd.SetOutput(output)
|
||||
err := bcryptCmd.Execute()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = bcrypt.CompareHashAndPassword(output.Bytes(), []byte("abc"))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -22,13 +22,13 @@ func PrintResource(resource interface{}, output string) error {
|
||||
case "json":
|
||||
jsonBytes, err := json.MarshalIndent(resource, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to marshal resource to json: %w", err)
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(jsonBytes))
|
||||
case "yaml":
|
||||
yamlBytes, err := yaml.Marshal(resource)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to marshal resource to yaml: %w", err)
|
||||
return err
|
||||
}
|
||||
fmt.Print(string(yamlBytes))
|
||||
default:
|
||||
@@ -56,13 +56,13 @@ func PrintResourceList(resources interface{}, output string, single bool) error
|
||||
case "json":
|
||||
jsonBytes, err := json.MarshalIndent(resources, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to marshal resources to json: %w", err)
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(jsonBytes))
|
||||
case "yaml":
|
||||
yamlBytes, err := yaml.Marshal(resources)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to marshal resources to yaml: %w", err)
|
||||
return err
|
||||
}
|
||||
fmt.Print(string(yamlBytes))
|
||||
default:
|
||||
|
||||
@@ -38,11 +38,12 @@ import (
|
||||
)
|
||||
|
||||
type forwardCacheClient struct {
|
||||
namespace string
|
||||
context string
|
||||
init sync.Once
|
||||
client cache.CacheClient
|
||||
err error
|
||||
namespace string
|
||||
context string
|
||||
init sync.Once
|
||||
client cache.CacheClient
|
||||
compression cache.RedisCompressionType
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *forwardCacheClient) doLazy(action func(client cache.CacheClient) error) error {
|
||||
@@ -58,7 +59,7 @@ func (c *forwardCacheClient) doLazy(action func(client cache.CacheClient) error)
|
||||
}
|
||||
|
||||
redisClient := redis.NewClient(&redis.Options{Addr: fmt.Sprintf("localhost:%d", redisPort)})
|
||||
c.client = cache.NewRedisCache(redisClient, time.Hour, cache.RedisCompressionNone)
|
||||
c.client = cache.NewRedisCache(redisClient, time.Hour, c.compression)
|
||||
})
|
||||
if c.err != nil {
|
||||
return c.err
|
||||
@@ -139,7 +140,7 @@ func testAPI(ctx context.Context, clientOpts *apiclient.ClientOptions) error {
|
||||
|
||||
// StartLocalServer allows executing command in a headless mode: on the fly starts Argo CD API server and
|
||||
// changes provided client options to use started API server port
|
||||
func StartLocalServer(ctx context.Context, clientOpts *apiclient.ClientOptions, ctxStr string, port *int, address *string) error {
|
||||
func StartLocalServer(ctx context.Context, clientOpts *apiclient.ClientOptions, ctxStr string, port *int, address *string, compression cache.RedisCompressionType) error {
|
||||
flags := pflag.NewFlagSet("tmp", pflag.ContinueOnError)
|
||||
clientConfig := cli.AddKubectlFlagsToSet(flags)
|
||||
startInProcessAPI := clientOpts.Core
|
||||
@@ -200,7 +201,7 @@ func StartLocalServer(ctx context.Context, clientOpts *apiclient.ClientOptions,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appstateCache := appstatecache.NewCache(cache.NewCache(&forwardCacheClient{namespace: namespace, context: ctxStr}), time.Hour)
|
||||
appstateCache := appstatecache.NewCache(cache.NewCache(&forwardCacheClient{namespace: namespace, context: ctxStr, compression: compression}), time.Hour)
|
||||
srv := server.NewServer(ctx, server.ArgoCDServerOpts{
|
||||
EnableGZip: false,
|
||||
Namespace: namespace,
|
||||
@@ -242,7 +243,7 @@ func NewClientOrDie(opts *apiclient.ClientOptions, c *cobra.Command) apiclient.C
|
||||
ctx := c.Context()
|
||||
|
||||
ctxStr := initialize.RetrieveContextIfChanged(c.Flag("context"))
|
||||
err := StartLocalServer(ctx, opts, ctxStr, nil, nil)
|
||||
err := StartLocalServer(ctx, opts, ctxStr, nil, nil, cache.RedisCompressionNone)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -863,23 +863,23 @@ func NewProjectEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
|
||||
cli.InteractiveEdit(fmt.Sprintf("%s-*-edit.yaml", projName), projData, func(input []byte) error {
|
||||
input, err = yaml.YAMLToJSON(input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error converting YAML to JSON: %w", err)
|
||||
return err
|
||||
}
|
||||
updatedSpec := v1alpha1.AppProjectSpec{}
|
||||
err = json.Unmarshal(input, &updatedSpec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error unmarshaling input into application spec: %w", err)
|
||||
return err
|
||||
}
|
||||
proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get project by project name: %w", err)
|
||||
return err
|
||||
}
|
||||
proj.Spec = updatedSpec
|
||||
_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update project:\n%w", err)
|
||||
return fmt.Errorf("Failed to update project:\n%v", err)
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package commands
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"text/tabwriter"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -233,15 +234,12 @@ func printRepoTable(repos appsv1.Repositories) {
|
||||
_, _ = fmt.Fprintf(w, "TYPE\tNAME\tREPO\tINSECURE\tOCI\tLFS\tCREDS\tSTATUS\tMESSAGE\tPROJECT\n")
|
||||
for _, r := range repos {
|
||||
var hasCreds string
|
||||
if !r.HasCredentials() {
|
||||
hasCreds = "false"
|
||||
if r.InheritedCreds {
|
||||
hasCreds = "inherited"
|
||||
} else {
|
||||
if r.InheritedCreds {
|
||||
hasCreds = "inherited"
|
||||
} else {
|
||||
hasCreds = "true"
|
||||
}
|
||||
hasCreds = strconv.FormatBool(r.HasCredentials())
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%v\t%v\t%s\t%s\t%s\t%s\n", r.Type, r.Name, r.Repo, r.IsInsecure(), r.EnableOCI, r.EnableLFS, hasCreds, r.ConnectionState.Status, r.ConnectionState.Message, r.Project)
|
||||
}
|
||||
_ = w.Flush()
|
||||
|
||||
@@ -41,7 +41,7 @@ func NewCommand() *cobra.Command {
|
||||
}
|
||||
|
||||
command.AddCommand(NewCompletionCommand())
|
||||
command.AddCommand(initialize.InitCommand(NewVersionCmd(&clientOpts, nil)))
|
||||
command.AddCommand(initialize.InitCommand(NewVersionCmd(&clientOpts)))
|
||||
command.AddCommand(initialize.InitCommand(NewClusterCommand(&clientOpts, pathOpts)))
|
||||
command.AddCommand(initialize.InitCommand(NewApplicationCommand(&clientOpts)))
|
||||
command.AddCommand(initialize.InitCommand(NewAppSetCommand(&clientOpts)))
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
// NewVersionCmd returns a new `version` command to be used as a sub-command to root
|
||||
func NewVersionCmd(clientOpts *argocdclient.ClientOptions, serverVersion *version.VersionMessage) *cobra.Command {
|
||||
func NewVersionCmd(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
short bool
|
||||
client bool
|
||||
@@ -54,12 +54,7 @@ func NewVersionCmd(clientOpts *argocdclient.ClientOptions, serverVersion *versio
|
||||
}
|
||||
|
||||
if !client {
|
||||
var sv *version.VersionMessage
|
||||
if serverVersion == nil {
|
||||
sv = getServerVersion(ctx, clientOpts, cmd)
|
||||
} else {
|
||||
sv = serverVersion
|
||||
}
|
||||
sv := getServerVersion(ctx, clientOpts, cmd)
|
||||
|
||||
if short {
|
||||
v["server"] = map[string]string{"argocd-server": sv.Version}
|
||||
@@ -73,13 +68,8 @@ func NewVersionCmd(clientOpts *argocdclient.ClientOptions, serverVersion *versio
|
||||
case "wide", "short", "":
|
||||
fmt.Fprint(cmd.OutOrStdout(), printClientVersion(&cv, short || (output == "short")))
|
||||
if !client {
|
||||
var sv *version.VersionMessage
|
||||
if serverVersion == nil {
|
||||
sv = getServerVersion(ctx, clientOpts, cmd)
|
||||
} else {
|
||||
sv = serverVersion
|
||||
}
|
||||
fmt.Fprint(cmd.OutOrStdout(), printServerVersion(sv, short || (output == "short")))
|
||||
sv := getServerVersion(ctx, clientOpts, cmd)
|
||||
printServerVersion(sv, short || (output == "short"))
|
||||
}
|
||||
default:
|
||||
log.Fatalf("unknown output format: %s", output)
|
||||
@@ -119,45 +109,44 @@ func printClientVersion(version *common.Version, short bool) string {
|
||||
return output
|
||||
}
|
||||
|
||||
func printServerVersion(version *version.VersionMessage, short bool) string {
|
||||
output := fmt.Sprintf("%s: %s\n", "argocd-server", version.Version)
|
||||
func printServerVersion(version *version.VersionMessage, short bool) {
|
||||
fmt.Printf("%s: %s\n", "argocd-server", version.Version)
|
||||
|
||||
if short {
|
||||
return output
|
||||
return
|
||||
}
|
||||
|
||||
if version.BuildDate != "" {
|
||||
output += fmt.Sprintf(" BuildDate: %s\n", version.BuildDate)
|
||||
fmt.Printf(" BuildDate: %s\n", version.BuildDate)
|
||||
}
|
||||
if version.GitCommit != "" {
|
||||
output += fmt.Sprintf(" GitCommit: %s\n", version.GitCommit)
|
||||
fmt.Printf(" GitCommit: %s\n", version.GitCommit)
|
||||
}
|
||||
if version.GitTreeState != "" {
|
||||
output += fmt.Sprintf(" GitTreeState: %s\n", version.GitTreeState)
|
||||
fmt.Printf(" GitTreeState: %s\n", version.GitTreeState)
|
||||
}
|
||||
if version.GitTag != "" {
|
||||
output += fmt.Sprintf(" GitTag: %s\n", version.GitTag)
|
||||
fmt.Printf(" GitTag: %s\n", version.GitTag)
|
||||
}
|
||||
if version.GoVersion != "" {
|
||||
output += fmt.Sprintf(" GoVersion: %s\n", version.GoVersion)
|
||||
fmt.Printf(" GoVersion: %s\n", version.GoVersion)
|
||||
}
|
||||
if version.Compiler != "" {
|
||||
output += fmt.Sprintf(" Compiler: %s\n", version.Compiler)
|
||||
fmt.Printf(" Compiler: %s\n", version.Compiler)
|
||||
}
|
||||
if version.Platform != "" {
|
||||
output += fmt.Sprintf(" Platform: %s\n", version.Platform)
|
||||
fmt.Printf(" Platform: %s\n", version.Platform)
|
||||
}
|
||||
if version.KustomizeVersion != "" {
|
||||
output += fmt.Sprintf(" Kustomize Version: %s\n", version.KustomizeVersion)
|
||||
fmt.Printf(" Kustomize Version: %s\n", version.KustomizeVersion)
|
||||
}
|
||||
if version.HelmVersion != "" {
|
||||
output += fmt.Sprintf(" Helm Version: %s\n", version.HelmVersion)
|
||||
fmt.Printf(" Helm Version: %s\n", version.HelmVersion)
|
||||
}
|
||||
if version.KubectlVersion != "" {
|
||||
output += fmt.Sprintf(" Kubectl Version: %s\n", version.KubectlVersion)
|
||||
fmt.Printf(" Kubectl Version: %s\n", version.KubectlVersion)
|
||||
}
|
||||
if version.JsonnetVersion != "" {
|
||||
output += fmt.Sprintf(" Jsonnet Version: %s\n", version.JsonnetVersion)
|
||||
fmt.Printf(" Jsonnet Version: %s\n", version.JsonnetVersion)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
@@ -5,13 +5,12 @@ import (
|
||||
"testing"
|
||||
|
||||
argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apiclient/version"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestShortVersionClient(t *testing.T) {
|
||||
func TestShortVersion(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := NewVersionCmd(&argocdclient.ClientOptions{}, nil)
|
||||
cmd := NewVersionCmd(&argocdclient.ClientOptions{})
|
||||
cmd.SetOutput(buf)
|
||||
cmd.SetArgs([]string{"version", "--short", "--client"})
|
||||
err := cmd.Execute()
|
||||
@@ -21,17 +20,3 @@ func TestShortVersionClient(t *testing.T) {
|
||||
output := buf.String()
|
||||
assert.Equal(t, output, "argocd: v99.99.99+unknown\n")
|
||||
}
|
||||
|
||||
func TestShortVersion(t *testing.T) {
|
||||
serverVersion := &version.VersionMessage{Version: "v99.99.99+unknown"}
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := NewVersionCmd(&argocdclient.ClientOptions{}, serverVersion)
|
||||
cmd.SetOutput(buf)
|
||||
cmd.SetArgs([]string{"argocd", "version", "--short"})
|
||||
err := cmd.Execute()
|
||||
if err != nil {
|
||||
t.Fatal("Failed to execute short version command")
|
||||
}
|
||||
output := buf.String()
|
||||
assert.Equal(t, output, "argocd: v99.99.99+unknown\nargocd-server: v99.99.99+unknown\n")
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func readAppsetFromURI(fileURL string, appset *[]*argoprojiov1alpha1.Application
|
||||
|
||||
yml, err := readFilePayload()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading file payload: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return readAppset(yml, appset)
|
||||
@@ -49,18 +49,18 @@ func readAppsetFromURI(fileURL string, appset *[]*argoprojiov1alpha1.Application
|
||||
func readAppset(yml []byte, appsets *[]*argoprojiov1alpha1.ApplicationSet) error {
|
||||
yamls, err := kube.SplitYAMLToString(yml)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error splitting YAML to string: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, yml := range yamls {
|
||||
var appset argoprojiov1alpha1.ApplicationSet
|
||||
err = config.Unmarshal([]byte(yml), &appset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error unmarshalling appset: %w", err)
|
||||
return err
|
||||
}
|
||||
*appsets = append(*appsets, &appset)
|
||||
|
||||
}
|
||||
|
||||
return fmt.Errorf("error reading app set: %w", err)
|
||||
// we reach here if there is no error found while reading the Application Set
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ func readProjFromURI(fileURL string, proj *v1alpha1.AppProject) error {
|
||||
} else {
|
||||
err = config.UnmarshalRemoteFile(fileURL, &proj)
|
||||
}
|
||||
return fmt.Errorf("error reading proj from uri: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
func SetProjSpecOptions(flags *pflag.FlagSet, spec *v1alpha1.AppProjectSpec, projOpts *ProjectOpts) int {
|
||||
|
||||
@@ -6,7 +6,6 @@ package apiclient
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
apiclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
@@ -363,55 +362,6 @@ func (m *RepositoryResponse) GetIsSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ParametersAnnouncementResponse contains a list of announcements. This list represents all the parameters which a CMP
|
||||
// is able to accept.
|
||||
type ParametersAnnouncementResponse struct {
|
||||
ParameterAnnouncements []*apiclient.ParameterAnnouncement `protobuf:"bytes,1,rep,name=parameterAnnouncements,proto3" json:"parameterAnnouncements,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ParametersAnnouncementResponse) Reset() { *m = ParametersAnnouncementResponse{} }
|
||||
func (m *ParametersAnnouncementResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*ParametersAnnouncementResponse) ProtoMessage() {}
|
||||
func (*ParametersAnnouncementResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_b21875a7079a06ed, []int{5}
|
||||
}
|
||||
func (m *ParametersAnnouncementResponse) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *ParametersAnnouncementResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_ParametersAnnouncementResponse.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *ParametersAnnouncementResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ParametersAnnouncementResponse.Merge(m, src)
|
||||
}
|
||||
func (m *ParametersAnnouncementResponse) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *ParametersAnnouncementResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ParametersAnnouncementResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ParametersAnnouncementResponse proto.InternalMessageInfo
|
||||
|
||||
func (m *ParametersAnnouncementResponse) GetParameterAnnouncements() []*apiclient.ParameterAnnouncement {
|
||||
if m != nil {
|
||||
return m.ParameterAnnouncements
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Chunk []byte `protobuf:"bytes,1,opt,name=chunk,proto3" json:"chunk,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
@@ -423,7 +373,7 @@ func (m *File) Reset() { *m = File{} }
|
||||
func (m *File) String() string { return proto.CompactTextString(m) }
|
||||
func (*File) ProtoMessage() {}
|
||||
func (*File) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_b21875a7079a06ed, []int{6}
|
||||
return fileDescriptor_b21875a7079a06ed, []int{5}
|
||||
}
|
||||
func (m *File) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@@ -465,49 +415,44 @@ func init() {
|
||||
proto.RegisterType((*EnvEntry)(nil), "plugin.EnvEntry")
|
||||
proto.RegisterType((*ManifestResponse)(nil), "plugin.ManifestResponse")
|
||||
proto.RegisterType((*RepositoryResponse)(nil), "plugin.RepositoryResponse")
|
||||
proto.RegisterType((*ParametersAnnouncementResponse)(nil), "plugin.ParametersAnnouncementResponse")
|
||||
proto.RegisterType((*File)(nil), "plugin.File")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("cmpserver/plugin/plugin.proto", fileDescriptor_b21875a7079a06ed) }
|
||||
|
||||
var fileDescriptor_b21875a7079a06ed = []byte{
|
||||
// 558 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x54, 0xc1, 0x6e, 0xd3, 0x4c,
|
||||
0x10, 0xae, 0x9b, 0xb4, 0x4d, 0x26, 0x95, 0xfe, 0x68, 0xf5, 0x0b, 0x4c, 0xd4, 0x86, 0xe0, 0x03,
|
||||
0xca, 0x85, 0x44, 0x32, 0x88, 0x1b, 0x12, 0x2d, 0x2a, 0xad, 0x40, 0x41, 0xd1, 0x96, 0x0b, 0xdc,
|
||||
0xb6, 0xce, 0x24, 0x59, 0x6a, 0xef, 0x2e, 0xeb, 0xb5, 0xa5, 0xc0, 0x85, 0xf7, 0xe0, 0x01, 0x78,
|
||||
0x15, 0x8e, 0x3c, 0x02, 0xca, 0x93, 0x20, 0xaf, 0xed, 0xd8, 0xa2, 0x6d, 0x38, 0x79, 0xe6, 0x9b,
|
||||
0x99, 0x6f, 0xbf, 0x9d, 0x99, 0x35, 0x1c, 0x07, 0x91, 0x8a, 0x51, 0xa7, 0xa8, 0xc7, 0x2a, 0x4c,
|
||||
0x16, 0x5c, 0x14, 0x9f, 0x91, 0xd2, 0xd2, 0x48, 0xb2, 0x9f, 0x7b, 0xbd, 0xb3, 0x05, 0x37, 0xcb,
|
||||
0xe4, 0x6a, 0x14, 0xc8, 0x68, 0xcc, 0xf4, 0x42, 0x2a, 0x2d, 0x3f, 0x59, 0xe3, 0x49, 0x30, 0x1b,
|
||||
0xa7, 0xfe, 0x58, 0xa3, 0x92, 0x05, 0x8d, 0x35, 0xb9, 0x91, 0x7a, 0x55, 0x33, 0x73, 0x3a, 0xef,
|
||||
0x9b, 0x03, 0xdd, 0x13, 0xa5, 0x2e, 0x8d, 0x46, 0x16, 0x51, 0xfc, 0x9c, 0x60, 0x6c, 0xc8, 0x0b,
|
||||
0x68, 0x45, 0x68, 0xd8, 0x8c, 0x19, 0xe6, 0x3a, 0x03, 0x67, 0xd8, 0xf1, 0x1f, 0x8e, 0x0a, 0x11,
|
||||
0x13, 0x26, 0xf8, 0x1c, 0x63, 0x53, 0xa4, 0x4e, 0x8a, 0xb4, 0x8b, 0x1d, 0xba, 0x29, 0x21, 0x1e,
|
||||
0x34, 0xe7, 0x3c, 0x44, 0x77, 0xd7, 0x96, 0x1e, 0x96, 0xa5, 0xaf, 0x79, 0x88, 0x17, 0x3b, 0xd4,
|
||||
0xc6, 0x4e, 0xdb, 0x70, 0xa0, 0x73, 0x0a, 0xef, 0x87, 0x03, 0xf7, 0xef, 0xa0, 0x25, 0x2e, 0x1c,
|
||||
0x30, 0xa5, 0xde, 0xb1, 0x08, 0xad, 0x90, 0x36, 0x2d, 0x5d, 0xd2, 0x07, 0x60, 0x4a, 0x51, 0x0c,
|
||||
0xa7, 0xcc, 0x2c, 0xed, 0x51, 0x6d, 0x5a, 0x43, 0x48, 0x0f, 0x5a, 0xc1, 0x12, 0x83, 0xeb, 0x38,
|
||||
0x89, 0xdc, 0x86, 0x8d, 0x6e, 0x7c, 0x42, 0xa0, 0x19, 0xf3, 0x2f, 0xe8, 0x36, 0x07, 0xce, 0xb0,
|
||||
0x41, 0xad, 0x4d, 0x3c, 0x68, 0xa0, 0x48, 0xdd, 0xbd, 0x41, 0x63, 0xd8, 0xf1, 0xbb, 0xa5, 0xe6,
|
||||
0x33, 0x91, 0x9e, 0x09, 0xa3, 0x57, 0x34, 0x0b, 0x7a, 0xcf, 0xa0, 0x55, 0x02, 0x19, 0x87, 0xa8,
|
||||
0x64, 0x59, 0x9b, 0xfc, 0x0f, 0x7b, 0x29, 0x0b, 0x13, 0x2c, 0xe4, 0xe4, 0x8e, 0x37, 0x85, 0x6e,
|
||||
0x75, 0xbd, 0x58, 0x49, 0x11, 0x23, 0x39, 0x82, 0x76, 0x54, 0x60, 0xb1, 0xeb, 0x0c, 0x1a, 0xc3,
|
||||
0x36, 0xad, 0x80, 0xec, 0x6e, 0xb1, 0x4c, 0x74, 0x80, 0xef, 0x57, 0xaa, 0x24, 0xab, 0x21, 0xde,
|
||||
0x73, 0x20, 0x74, 0x33, 0xc8, 0x0d, 0xe7, 0x00, 0x3a, 0x3c, 0xbe, 0x4c, 0x94, 0x92, 0xda, 0xe0,
|
||||
0xcc, 0x0a, 0x6b, 0xd1, 0x3a, 0xe4, 0x7d, 0x85, 0xfe, 0x94, 0x69, 0x16, 0xa1, 0x41, 0x1d, 0x9f,
|
||||
0x08, 0x21, 0x13, 0x11, 0x60, 0x84, 0xa2, 0xd2, 0xf5, 0x01, 0xee, 0xa9, 0x32, 0xa3, 0x9e, 0x90,
|
||||
0x8b, 0xec, 0xf8, 0x8f, 0x46, 0xb5, 0x0d, 0x9a, 0xde, 0x96, 0x49, 0xef, 0x20, 0xf0, 0x8e, 0xa0,
|
||||
0x99, 0x6d, 0x40, 0xd6, 0xa4, 0x60, 0x99, 0x88, 0x6b, 0x2b, 0xf0, 0x90, 0xe6, 0x8e, 0xff, 0x7d,
|
||||
0x17, 0x8e, 0x5f, 0x49, 0x31, 0xe7, 0x8b, 0x09, 0x13, 0x6c, 0x61, 0x6b, 0xa6, 0x76, 0x06, 0x97,
|
||||
0xa8, 0x53, 0x1e, 0x20, 0x79, 0x03, 0xdd, 0x73, 0x14, 0xa8, 0x99, 0xc1, 0xb2, 0x9d, 0xc4, 0x2d,
|
||||
0xe7, 0xf4, 0xf7, 0x0a, 0xf7, 0xdc, 0x9b, 0x0b, 0x9b, 0x5f, 0xd1, 0xdb, 0x19, 0x3a, 0xe4, 0x2d,
|
||||
0xfc, 0x37, 0x61, 0x26, 0x58, 0x56, 0x5d, 0xdc, 0x42, 0xd5, 0x2b, 0x23, 0x37, 0x7b, 0x6e, 0xc9,
|
||||
0x18, 0x3c, 0x38, 0x47, 0x73, 0x7b, 0x63, 0xb7, 0xd0, 0x3e, 0x2e, 0x23, 0xdb, 0x47, 0x92, 0x1d,
|
||||
0x71, 0xfa, 0xf2, 0xe7, 0xba, 0xef, 0xfc, 0x5a, 0xf7, 0x9d, 0xdf, 0xeb, 0xbe, 0xf3, 0xd1, 0xff,
|
||||
0xc7, 0xd3, 0xaf, 0x7e, 0x20, 0x4c, 0xf1, 0x20, 0xe4, 0x28, 0xcc, 0xd5, 0xbe, 0x7d, 0xee, 0x4f,
|
||||
0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x33, 0x34, 0xb3, 0x95, 0x5e, 0x04, 0x00, 0x00,
|
||||
// 483 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x53, 0xd1, 0x8e, 0x12, 0x3d,
|
||||
0x14, 0xa6, 0x3f, 0xec, 0x2e, 0x1c, 0x36, 0xf9, 0x49, 0x63, 0xe2, 0x84, 0xec, 0x22, 0x99, 0x2b,
|
||||
0x6e, 0x84, 0x04, 0x8d, 0x77, 0x26, 0xba, 0x66, 0x75, 0xa3, 0xc1, 0x90, 0xe2, 0x95, 0x77, 0xdd,
|
||||
0x72, 0x80, 0xba, 0x33, 0x6d, 0x6d, 0x3b, 0x93, 0xe0, 0x95, 0x6f, 0xe3, 0x2b, 0xf8, 0x08, 0x5e,
|
||||
0xfa, 0x08, 0x86, 0x27, 0x31, 0x53, 0x66, 0x18, 0xe2, 0x46, 0xaf, 0x38, 0xdf, 0x77, 0xce, 0xf9,
|
||||
0xf8, 0xbe, 0x4e, 0x0b, 0x97, 0x22, 0x35, 0x0e, 0x6d, 0x8e, 0x76, 0x62, 0x92, 0x6c, 0x2d, 0x55,
|
||||
0xf9, 0x33, 0x36, 0x56, 0x7b, 0x4d, 0x4f, 0xf7, 0x28, 0xfe, 0x4a, 0xa0, 0xf7, 0xd2, 0x98, 0x85,
|
||||
0xb7, 0xc8, 0x53, 0x86, 0x9f, 0x33, 0x74, 0x9e, 0x3e, 0x87, 0x76, 0x8a, 0x9e, 0x2f, 0xb9, 0xe7,
|
||||
0x11, 0x19, 0x92, 0x51, 0x77, 0xfa, 0x68, 0x5c, 0x6e, 0xcf, 0xb8, 0x92, 0x2b, 0x74, 0xbe, 0x1c,
|
||||
0x9d, 0x95, 0x63, 0x37, 0x0d, 0x76, 0x58, 0xa1, 0x31, 0xb4, 0x56, 0x32, 0xc1, 0xe8, 0xbf, 0xb0,
|
||||
0x7a, 0x5e, 0xad, 0xbe, 0x96, 0x09, 0xde, 0x34, 0x58, 0xe8, 0x5d, 0x75, 0xe0, 0xcc, 0xee, 0x25,
|
||||
0xe2, 0x6f, 0x04, 0x1e, 0xfe, 0x45, 0x96, 0x46, 0x70, 0xc6, 0x8d, 0x79, 0xcf, 0x53, 0x0c, 0x46,
|
||||
0x3a, 0xac, 0x82, 0x74, 0x00, 0xc0, 0x8d, 0x61, 0x98, 0xcc, 0xb9, 0xdf, 0x84, 0xbf, 0xea, 0xb0,
|
||||
0x23, 0x86, 0xf6, 0xa1, 0x2d, 0x36, 0x28, 0xee, 0x5c, 0x96, 0x46, 0xcd, 0xd0, 0x3d, 0x60, 0x4a,
|
||||
0xa1, 0xe5, 0xe4, 0x17, 0x8c, 0x5a, 0x43, 0x32, 0x6a, 0xb2, 0x50, 0xd3, 0x18, 0x9a, 0xa8, 0xf2,
|
||||
0xe8, 0x64, 0xd8, 0x1c, 0x75, 0xa7, 0xbd, 0xca, 0xf3, 0xb5, 0xca, 0xaf, 0x95, 0xb7, 0x5b, 0x56,
|
||||
0x34, 0xe3, 0xa7, 0xd0, 0xae, 0x88, 0x42, 0x43, 0xd5, 0xb6, 0x42, 0x4d, 0x1f, 0xc0, 0x49, 0xce,
|
||||
0x93, 0x0c, 0x4b, 0x3b, 0x7b, 0x10, 0xcf, 0xa1, 0x57, 0xc7, 0x73, 0x46, 0x2b, 0x87, 0xf4, 0x02,
|
||||
0x3a, 0x69, 0xc9, 0xb9, 0x88, 0x0c, 0x9b, 0xa3, 0x0e, 0xab, 0x89, 0x22, 0x9b, 0xd3, 0x99, 0x15,
|
||||
0xf8, 0x61, 0x6b, 0x2a, 0xb1, 0x23, 0x26, 0x7e, 0x06, 0x94, 0xa1, 0xd1, 0x4e, 0x7a, 0x6d, 0xb7,
|
||||
0x07, 0xcd, 0x21, 0x74, 0xa5, 0x5b, 0x64, 0xc6, 0x68, 0xeb, 0x71, 0x19, 0x8c, 0xb5, 0xd9, 0x31,
|
||||
0x15, 0x5f, 0x40, 0xab, 0xf8, 0x08, 0x85, 0x4f, 0xb1, 0xc9, 0xd4, 0x5d, 0x98, 0x39, 0x67, 0x7b,
|
||||
0x30, 0xfd, 0x4e, 0xe0, 0xf2, 0x95, 0x56, 0x2b, 0xb9, 0x9e, 0x71, 0xc5, 0xd7, 0x98, 0xa2, 0xf2,
|
||||
0xf3, 0x70, 0x0c, 0x0b, 0xb4, 0xb9, 0x14, 0x48, 0xdf, 0x42, 0xef, 0x0d, 0x2a, 0xb4, 0xdc, 0x63,
|
||||
0x95, 0x88, 0x46, 0xd5, 0x51, 0xfd, 0x79, 0x8b, 0xfa, 0xd1, 0xfd, 0x3b, 0xb3, 0x77, 0x1a, 0x37,
|
||||
0x46, 0x84, 0xbe, 0x83, 0xff, 0x67, 0xdc, 0x8b, 0x4d, 0x1d, 0xe4, 0x1f, 0x52, 0xfd, 0xaa, 0x73,
|
||||
0x3f, 0x76, 0x21, 0x76, 0xf5, 0xe2, 0xc7, 0x6e, 0x40, 0x7e, 0xee, 0x06, 0xe4, 0xd7, 0x6e, 0x40,
|
||||
0x3e, 0x4e, 0xd7, 0xd2, 0x6f, 0xb2, 0xdb, 0xb1, 0xd0, 0xe9, 0x84, 0xdb, 0xb5, 0x36, 0x56, 0x7f,
|
||||
0x0a, 0xc5, 0x63, 0xb1, 0x9c, 0xe4, 0xd3, 0x49, 0xfd, 0x32, 0xb8, 0x91, 0x22, 0x91, 0xa8, 0xfc,
|
||||
0xed, 0x69, 0x78, 0x16, 0x4f, 0x7e, 0x07, 0x00, 0x00, 0xff, 0xff, 0x83, 0x01, 0x5e, 0x48, 0x37,
|
||||
0x03, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
@@ -527,8 +472,6 @@ type ConfigManagementPluginServiceClient interface {
|
||||
GenerateManifest(ctx context.Context, opts ...grpc.CallOption) (ConfigManagementPluginService_GenerateManifestClient, error)
|
||||
// MatchRepository returns whether or not the given application is supported by the plugin
|
||||
MatchRepository(ctx context.Context, opts ...grpc.CallOption) (ConfigManagementPluginService_MatchRepositoryClient, error)
|
||||
// GetParametersAnnouncement gets a list of parameter announcements for the given app
|
||||
GetParametersAnnouncement(ctx context.Context, opts ...grpc.CallOption) (ConfigManagementPluginService_GetParametersAnnouncementClient, error)
|
||||
}
|
||||
|
||||
type configManagementPluginServiceClient struct {
|
||||
@@ -607,40 +550,6 @@ func (x *configManagementPluginServiceMatchRepositoryClient) CloseAndRecv() (*Re
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *configManagementPluginServiceClient) GetParametersAnnouncement(ctx context.Context, opts ...grpc.CallOption) (ConfigManagementPluginService_GetParametersAnnouncementClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &_ConfigManagementPluginService_serviceDesc.Streams[2], "/plugin.ConfigManagementPluginService/GetParametersAnnouncement", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &configManagementPluginServiceGetParametersAnnouncementClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type ConfigManagementPluginService_GetParametersAnnouncementClient interface {
|
||||
Send(*AppStreamRequest) error
|
||||
CloseAndRecv() (*ParametersAnnouncementResponse, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type configManagementPluginServiceGetParametersAnnouncementClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *configManagementPluginServiceGetParametersAnnouncementClient) Send(m *AppStreamRequest) error {
|
||||
return x.ClientStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *configManagementPluginServiceGetParametersAnnouncementClient) CloseAndRecv() (*ParametersAnnouncementResponse, error) {
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := new(ParametersAnnouncementResponse)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// ConfigManagementPluginServiceServer is the server API for ConfigManagementPluginService service.
|
||||
type ConfigManagementPluginServiceServer interface {
|
||||
// GenerateManifests receive a stream containing a tgz archive with all required files necessary
|
||||
@@ -648,8 +557,6 @@ type ConfigManagementPluginServiceServer interface {
|
||||
GenerateManifest(ConfigManagementPluginService_GenerateManifestServer) error
|
||||
// MatchRepository returns whether or not the given application is supported by the plugin
|
||||
MatchRepository(ConfigManagementPluginService_MatchRepositoryServer) error
|
||||
// GetParametersAnnouncement gets a list of parameter announcements for the given app
|
||||
GetParametersAnnouncement(ConfigManagementPluginService_GetParametersAnnouncementServer) error
|
||||
}
|
||||
|
||||
// UnimplementedConfigManagementPluginServiceServer can be embedded to have forward compatible implementations.
|
||||
@@ -662,9 +569,6 @@ func (*UnimplementedConfigManagementPluginServiceServer) GenerateManifest(srv Co
|
||||
func (*UnimplementedConfigManagementPluginServiceServer) MatchRepository(srv ConfigManagementPluginService_MatchRepositoryServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method MatchRepository not implemented")
|
||||
}
|
||||
func (*UnimplementedConfigManagementPluginServiceServer) GetParametersAnnouncement(srv ConfigManagementPluginService_GetParametersAnnouncementServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method GetParametersAnnouncement not implemented")
|
||||
}
|
||||
|
||||
func RegisterConfigManagementPluginServiceServer(s *grpc.Server, srv ConfigManagementPluginServiceServer) {
|
||||
s.RegisterService(&_ConfigManagementPluginService_serviceDesc, srv)
|
||||
@@ -722,32 +626,6 @@ func (x *configManagementPluginServiceMatchRepositoryServer) Recv() (*AppStreamR
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func _ConfigManagementPluginService_GetParametersAnnouncement_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(ConfigManagementPluginServiceServer).GetParametersAnnouncement(&configManagementPluginServiceGetParametersAnnouncementServer{stream})
|
||||
}
|
||||
|
||||
type ConfigManagementPluginService_GetParametersAnnouncementServer interface {
|
||||
SendAndClose(*ParametersAnnouncementResponse) error
|
||||
Recv() (*AppStreamRequest, error)
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type configManagementPluginServiceGetParametersAnnouncementServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *configManagementPluginServiceGetParametersAnnouncementServer) SendAndClose(m *ParametersAnnouncementResponse) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *configManagementPluginServiceGetParametersAnnouncementServer) Recv() (*AppStreamRequest, error) {
|
||||
m := new(AppStreamRequest)
|
||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
var _ConfigManagementPluginService_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "plugin.ConfigManagementPluginService",
|
||||
HandlerType: (*ConfigManagementPluginServiceServer)(nil),
|
||||
@@ -763,11 +641,6 @@ var _ConfigManagementPluginService_serviceDesc = grpc.ServiceDesc{
|
||||
Handler: _ConfigManagementPluginService_MatchRepository_Handler,
|
||||
ClientStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "GetParametersAnnouncement",
|
||||
Handler: _ConfigManagementPluginService_GetParametersAnnouncement_Handler,
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "cmpserver/plugin/plugin.proto",
|
||||
}
|
||||
@@ -1038,47 +911,6 @@ func (m *RepositoryResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *ParametersAnnouncementResponse) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *ParametersAnnouncementResponse) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *ParametersAnnouncementResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.XXX_unrecognized != nil {
|
||||
i -= len(m.XXX_unrecognized)
|
||||
copy(dAtA[i:], m.XXX_unrecognized)
|
||||
}
|
||||
if len(m.ParameterAnnouncements) > 0 {
|
||||
for iNdEx := len(m.ParameterAnnouncements) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
size, err := m.ParameterAnnouncements[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintPlugin(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *File) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
@@ -1253,24 +1085,6 @@ func (m *RepositoryResponse) Size() (n int) {
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *ParametersAnnouncementResponse) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.ParameterAnnouncements) > 0 {
|
||||
for _, e := range m.ParameterAnnouncements {
|
||||
l = e.Size()
|
||||
n += 1 + l + sovPlugin(uint64(l))
|
||||
}
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *File) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
@@ -1915,91 +1729,6 @@ func (m *RepositoryResponse) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *ParametersAnnouncementResponse) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowPlugin
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: ParametersAnnouncementResponse: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: ParametersAnnouncementResponse: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ParameterAnnouncements", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowPlugin
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthPlugin
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthPlugin
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.ParameterAnnouncements = append(m.ParameterAnnouncements, &apiclient.ParameterAnnouncement{})
|
||||
if err := m.ParameterAnnouncements[len(m.ParameterAnnouncements)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipPlugin(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthPlugin
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *File) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
configUtil "github.com/argoproj/argo-cd/v2/util/config"
|
||||
)
|
||||
|
||||
@@ -22,11 +21,10 @@ type PluginConfig struct {
|
||||
}
|
||||
|
||||
type PluginConfigSpec struct {
|
||||
Version string `json:"version"`
|
||||
Init Command `json:"init,omitempty"`
|
||||
Generate Command `json:"generate"`
|
||||
Discover Discover `json:"discover"`
|
||||
Parameters Parameters `yaml:"parameters"`
|
||||
Version string `json:"version"`
|
||||
Init Command `json:"init,omitempty"`
|
||||
Generate Command `json:"generate"`
|
||||
Discover Discover `json:"discover"`
|
||||
}
|
||||
|
||||
//Discover holds find and fileName
|
||||
@@ -47,17 +45,6 @@ type Find struct {
|
||||
Glob string `json:"glob"`
|
||||
}
|
||||
|
||||
// Parameters holds static and dynamic configurations
|
||||
type Parameters struct {
|
||||
Static []*apiclient.ParameterAnnouncement `yaml:"static"`
|
||||
Dynamic Command `yaml:"dynamic"`
|
||||
}
|
||||
|
||||
// Dynamic hold the dynamic announcements for CMP's
|
||||
type Dynamic struct {
|
||||
Command
|
||||
}
|
||||
|
||||
func ReadPluginConfig(filePath string) (*PluginConfig, error) {
|
||||
path := fmt.Sprintf("%s/%s", strings.TrimRight(filePath, "/"), common.PluginConfigFileName)
|
||||
|
||||
|
||||
@@ -3,20 +3,19 @@ package plugin
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/argoproj/pkg/rand"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/cmpserver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
repoclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/util/buffered_context"
|
||||
"github.com/argoproj/argo-cd/v2/util/cmp"
|
||||
"github.com/argoproj/argo-cd/v2/util/io/files"
|
||||
@@ -24,6 +23,8 @@ import (
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
"github.com/mattn/go-zglob"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/cmpserver/apiclient"
|
||||
)
|
||||
|
||||
// cmpTimeoutBuffer is the amount of time before the request deadline to timeout server-side work. It makes sure there's
|
||||
@@ -46,14 +47,15 @@ func NewService(initConstants CMPServerInitConstants) *Service {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) Init(workDir string) error {
|
||||
func (s *Service) Init() error {
|
||||
workDir := common.GetCMPWorkDir()
|
||||
err := os.RemoveAll(workDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error removing workdir %q: %w", workDir, err)
|
||||
return fmt.Errorf("error removing workdir %q: %s", workDir, err)
|
||||
}
|
||||
err = os.MkdirAll(workDir, 0700)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating workdir %q: %w", workDir, err)
|
||||
return fmt.Errorf("error creating workdir %q: %s", workDir, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -73,9 +75,8 @@ func runCommand(ctx context.Context, command Command, path string, env []string)
|
||||
}
|
||||
logCtx := log.WithFields(log.Fields{"execID": execId})
|
||||
|
||||
// log in a way we can copy-and-paste into a terminal
|
||||
args := strings.Join(cmd.Args, " ")
|
||||
logCtx.WithFields(log.Fields{"dir": cmd.Dir}).Info(args)
|
||||
argsToLog := getCommandArgsToLog(cmd)
|
||||
logCtx.WithFields(log.Fields{"dir": cmd.Dir}).Info(argsToLog)
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
@@ -106,7 +107,7 @@ func runCommand(ctx context.Context, command Command, path string, env []string)
|
||||
logCtx.WithFields(log.Fields{"duration": duration}).Debug(output)
|
||||
|
||||
if err != nil {
|
||||
err := newCmdError(args, errors.New(err.Error()), strings.TrimSpace(stderr.String()))
|
||||
err := newCmdError(argsToLog, errors.New(err.Error()), strings.TrimSpace(stderr.String()))
|
||||
logCtx.Error(err.Error())
|
||||
return strings.TrimSuffix(output, "\n"), err
|
||||
}
|
||||
@@ -114,6 +115,28 @@ func runCommand(ctx context.Context, command Command, path string, env []string)
|
||||
return strings.TrimSuffix(output, "\n"), nil
|
||||
}
|
||||
|
||||
// getCommandArgsToLog represents the given command in a way that we can copy-and-paste into a terminal
|
||||
func getCommandArgsToLog(cmd *exec.Cmd) string {
|
||||
var argsToLog []string
|
||||
for _, arg := range cmd.Args {
|
||||
containsSpace := false
|
||||
for _, r := range arg {
|
||||
if unicode.IsSpace(r) {
|
||||
containsSpace = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if containsSpace {
|
||||
// add quotes and escape any internal quotes
|
||||
argsToLog = append(argsToLog, strconv.Quote(arg))
|
||||
} else {
|
||||
argsToLog = append(argsToLog, arg)
|
||||
}
|
||||
}
|
||||
args := strings.Join(argsToLog, " ")
|
||||
return args
|
||||
}
|
||||
|
||||
type CmdError struct {
|
||||
Args string
|
||||
Stderr string
|
||||
@@ -143,50 +166,24 @@ func environ(envVars []*apiclient.EnvEntry) []string {
|
||||
return environ
|
||||
}
|
||||
|
||||
// getTempDirMustCleanup creates a temporary directory and returns a cleanup function.
|
||||
func getTempDirMustCleanup(baseDir string) (workDir string, cleanup func(), err error) {
|
||||
workDir, err = files.CreateTempDir(baseDir)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("error creating temp dir: %w", err)
|
||||
}
|
||||
cleanup = func() {
|
||||
if err := os.RemoveAll(workDir); err != nil {
|
||||
log.WithFields(map[string]interface{}{
|
||||
common.SecurityField: common.SecurityHigh,
|
||||
common.SecurityCWEField: 459,
|
||||
}).Errorf("Failed to clean up temp directory: %s", err)
|
||||
}
|
||||
}
|
||||
return workDir, cleanup, nil
|
||||
}
|
||||
|
||||
type Stream interface {
|
||||
Recv() (*apiclient.AppStreamRequest, error)
|
||||
Context() context.Context
|
||||
}
|
||||
|
||||
type GenerateManifestStream interface {
|
||||
Stream
|
||||
SendAndClose(response *apiclient.ManifestResponse) error
|
||||
}
|
||||
|
||||
// GenerateManifest runs generate command from plugin config file and returns generated manifest files
|
||||
func (s *Service) GenerateManifest(stream apiclient.ConfigManagementPluginService_GenerateManifestServer) error {
|
||||
return s.generateManifestGeneric(stream)
|
||||
}
|
||||
|
||||
func (s *Service) generateManifestGeneric(stream GenerateManifestStream) error {
|
||||
ctx, cancel := buffered_context.WithEarlierDeadline(stream.Context(), cmpTimeoutBuffer)
|
||||
defer cancel()
|
||||
workDir, cleanup, err := getTempDirMustCleanup(common.GetCMPWorkDir())
|
||||
workDir, err := files.CreateTempDir(common.GetCMPWorkDir())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating workdir for manifest generation: %w", err)
|
||||
return fmt.Errorf("error creating temp dir: %s", err)
|
||||
}
|
||||
defer cleanup()
|
||||
defer func() {
|
||||
if err := os.RemoveAll(workDir); err != nil {
|
||||
// we panic here as the workDir may contain sensitive information
|
||||
panic(fmt.Sprintf("error removing generate manifest workdir: %s", err))
|
||||
}
|
||||
}()
|
||||
|
||||
metadata, err := cmp.ReceiveRepoStream(ctx, stream, workDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generate manifest error receiving stream: %w", err)
|
||||
return fmt.Errorf("generate manifest error receiving stream: %s", err)
|
||||
}
|
||||
|
||||
appPath := filepath.Clean(filepath.Join(workDir, metadata.AppRelPath))
|
||||
@@ -195,11 +192,11 @@ func (s *Service) generateManifestGeneric(stream GenerateManifestStream) error {
|
||||
}
|
||||
response, err := s.generateManifest(ctx, appPath, metadata.GetEnv())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating manifests: %w", err)
|
||||
return fmt.Errorf("error generating manifests: %s", err)
|
||||
}
|
||||
err = stream.SendAndClose(response)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending manifest response: %w", err)
|
||||
return fmt.Errorf("error sending manifest response: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -242,46 +239,42 @@ func (s *Service) generateManifest(ctx context.Context, appDir string, envEntrie
|
||||
}, err
|
||||
}
|
||||
|
||||
type MatchRepositoryStream interface {
|
||||
Stream
|
||||
SendAndClose(response *apiclient.RepositoryResponse) error
|
||||
}
|
||||
|
||||
// MatchRepository receives the application stream and checks whether
|
||||
// its repository type is supported by the config management plugin
|
||||
// server.
|
||||
// The checks are implemented in the following order:
|
||||
// 1. If spec.Discover.FileName is provided it finds for a name match in Applications files
|
||||
// 2. If spec.Discover.Find.Glob is provided if finds for a glob match in Applications files
|
||||
// 3. Otherwise it runs the spec.Discover.Find.Command
|
||||
//The checks are implemented in the following order:
|
||||
// 1. If spec.Discover.FileName is provided it finds for a name match in Applications files
|
||||
// 2. If spec.Discover.Find.Glob is provided if finds for a glob match in Applications files
|
||||
// 3. Otherwise it runs the spec.Discover.Find.Command
|
||||
func (s *Service) MatchRepository(stream apiclient.ConfigManagementPluginService_MatchRepositoryServer) error {
|
||||
return s.matchRepositoryGeneric(stream)
|
||||
}
|
||||
|
||||
func (s *Service) matchRepositoryGeneric(stream MatchRepositoryStream) error {
|
||||
bufferedCtx, cancel := buffered_context.WithEarlierDeadline(stream.Context(), cmpTimeoutBuffer)
|
||||
defer cancel()
|
||||
|
||||
workDir, cleanup, err := getTempDirMustCleanup(common.GetCMPWorkDir())
|
||||
workDir, err := files.CreateTempDir(common.GetCMPWorkDir())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating workdir for repository matching: %w", err)
|
||||
return fmt.Errorf("error creating match repository workdir: %s", err)
|
||||
}
|
||||
defer cleanup()
|
||||
defer func() {
|
||||
if err := os.RemoveAll(workDir); err != nil {
|
||||
// we panic here as the workDir may contain sensitive information
|
||||
panic(fmt.Sprintf("error removing match repository workdir: %s", err))
|
||||
}
|
||||
}()
|
||||
|
||||
metadata, err := cmp.ReceiveRepoStream(bufferedCtx, stream, workDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("match repository error receiving stream: %w", err)
|
||||
return fmt.Errorf("match repository error receiving stream: %s", err)
|
||||
}
|
||||
|
||||
isSupported, err := s.matchRepository(bufferedCtx, workDir, metadata.GetEnv())
|
||||
if err != nil {
|
||||
return fmt.Errorf("match repository error: %w", err)
|
||||
return fmt.Errorf("match repository error: %s", err)
|
||||
}
|
||||
repoResponse := &apiclient.RepositoryResponse{IsSupported: isSupported}
|
||||
|
||||
err = stream.SendAndClose(repoResponse)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending match repository response: %w", err)
|
||||
return fmt.Errorf("error sending match repository response: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -293,7 +286,7 @@ func (s *Service) matchRepository(ctx context.Context, workdir string, envEntrie
|
||||
pattern := filepath.Join(workdir, config.Spec.Discover.FileName)
|
||||
matches, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
e := fmt.Errorf("error finding filename match for pattern %q: %w", pattern, err)
|
||||
e := fmt.Errorf("error finding filename match for pattern %q: %s", pattern, err)
|
||||
log.Debug(e)
|
||||
return false, e
|
||||
}
|
||||
@@ -307,7 +300,7 @@ func (s *Service) matchRepository(ctx context.Context, workdir string, envEntrie
|
||||
// https://github.com/golang/go/issues/11862
|
||||
matches, err := zglob.Glob(pattern)
|
||||
if err != nil {
|
||||
e := fmt.Errorf("error finding glob match for pattern %q: %w", pattern, err)
|
||||
e := fmt.Errorf("error finding glob match for pattern %q: %s", pattern, err)
|
||||
log.Debug(e)
|
||||
return false, e
|
||||
}
|
||||
@@ -323,7 +316,7 @@ func (s *Service) matchRepository(ctx context.Context, workdir string, envEntrie
|
||||
|
||||
find, err := runCommand(ctx, config.Spec.Discover.Find.Command, workdir, env)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error running find command: %w", err)
|
||||
return false, fmt.Errorf("error running find command: %s", err)
|
||||
}
|
||||
|
||||
if find != "" {
|
||||
@@ -331,66 +324,3 @@ func (s *Service) matchRepository(ctx context.Context, workdir string, envEntrie
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// ParametersAnnouncementStream defines an interface able to send/receive a stream of parameter announcements.
|
||||
type ParametersAnnouncementStream interface {
|
||||
Stream
|
||||
SendAndClose(response *apiclient.ParametersAnnouncementResponse) error
|
||||
}
|
||||
|
||||
// GetParametersAnnouncement gets parameter announcements for a given Application and repo contents.
|
||||
func (s *Service) GetParametersAnnouncement(stream apiclient.ConfigManagementPluginService_GetParametersAnnouncementServer) error {
|
||||
bufferedCtx, cancel := buffered_context.WithEarlierDeadline(stream.Context(), cmpTimeoutBuffer)
|
||||
defer cancel()
|
||||
|
||||
workDir, cleanup, err := getTempDirMustCleanup(common.GetCMPWorkDir())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating workdir for generating parameter announcements: %w", err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
metadata, err := cmp.ReceiveRepoStream(bufferedCtx, stream, workDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parameters announcement error receiving stream: %w", err)
|
||||
}
|
||||
appPath := filepath.Clean(filepath.Join(workDir, metadata.AppRelPath))
|
||||
if !strings.HasPrefix(appPath, workDir) {
|
||||
return fmt.Errorf("illegal appPath: out of workDir bound")
|
||||
}
|
||||
|
||||
repoResponse, err := getParametersAnnouncement(bufferedCtx, appPath, s.initConstants.PluginConfig.Spec.Parameters.Static, s.initConstants.PluginConfig.Spec.Parameters.Dynamic)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get parameters announcement error: %w", err)
|
||||
}
|
||||
|
||||
err = stream.SendAndClose(repoResponse)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending parameters announcement response: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getParametersAnnouncement(ctx context.Context, appDir string, announcements []*repoclient.ParameterAnnouncement, command Command) (*apiclient.ParametersAnnouncementResponse, error) {
|
||||
augmentedAnnouncements := announcements
|
||||
|
||||
if len(command.Command) > 0 {
|
||||
stdout, err := runCommand(ctx, command, appDir, os.Environ())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error executing dynamic parameter output command: %w", err)
|
||||
}
|
||||
|
||||
var dynamicParamAnnouncements []*repoclient.ParameterAnnouncement
|
||||
err = json.Unmarshal([]byte(stdout), &dynamicParamAnnouncements)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling dynamic parameter output into ParametersAnnouncementResponse: %w", err)
|
||||
}
|
||||
|
||||
// dynamic goes first, because static should take precedence by being later.
|
||||
augmentedAnnouncements = append(dynamicParamAnnouncements, announcements...)
|
||||
}
|
||||
|
||||
repoResponse := &apiclient.ParametersAnnouncementResponse{
|
||||
ParameterAnnouncements: augmentedAnnouncements,
|
||||
}
|
||||
return repoResponse, nil
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ option go_package = "github.com/argoproj/argo-cd/v2/cmpserver/apiclient";
|
||||
|
||||
package plugin;
|
||||
|
||||
import "github.com/argoproj/argo-cd/v2/reposerver/repository/repository.proto";
|
||||
|
||||
// AppStreamRequest is the request object used to send the application's
|
||||
// files over a stream.
|
||||
message AppStreamRequest {
|
||||
@@ -46,12 +44,6 @@ message RepositoryResponse {
|
||||
bool isSupported = 1;
|
||||
}
|
||||
|
||||
// ParametersAnnouncementResponse contains a list of announcements. This list represents all the parameters which a CMP
|
||||
// is able to accept.
|
||||
message ParametersAnnouncementResponse {
|
||||
repeated repository.ParameterAnnouncement parameterAnnouncements = 1;
|
||||
}
|
||||
|
||||
message File {
|
||||
bytes chunk = 1;
|
||||
}
|
||||
@@ -66,8 +58,4 @@ service ConfigManagementPluginService {
|
||||
// MatchRepository returns whether or not the given application is supported by the plugin
|
||||
rpc MatchRepository(stream AppStreamRequest) returns (RepositoryResponse) {
|
||||
}
|
||||
|
||||
// GetParametersAnnouncement gets a list of parameter announcements for the given app
|
||||
rpc GetParametersAnnouncement(stream AppStreamRequest) returns (ParametersAnnouncementResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"gopkg.in/yaml.v2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/cmpserver/apiclient"
|
||||
repoclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/test"
|
||||
"github.com/argoproj/argo-cd/v2/util/cmp"
|
||||
"github.com/argoproj/argo-cd/v2/util/tgzstream"
|
||||
)
|
||||
|
||||
func newService(configFilePath string) (*Service, error) {
|
||||
@@ -40,11 +31,6 @@ func newService(configFilePath string) (*Service, error) {
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (s *Service) WithGenerateCommand(command Command) *Service {
|
||||
s.initConstants.PluginConfig.Spec.Generate = command
|
||||
return s
|
||||
}
|
||||
|
||||
type pluginOpt func(*CMPServerInitConstants)
|
||||
|
||||
func withDiscover(d Discover) pluginOpt {
|
||||
@@ -119,19 +105,6 @@ func TestMatchRepository(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, match)
|
||||
})
|
||||
t.Run("will not match a pattern with a syntax error", func(t *testing.T) {
|
||||
// given
|
||||
d := Discover{
|
||||
FileName: "[",
|
||||
}
|
||||
f := setup(t, withDiscover(d))
|
||||
|
||||
// when
|
||||
_, err := f.service.matchRepository(context.Background(), f.path, f.env)
|
||||
|
||||
// then
|
||||
assert.ErrorContains(t, err, "syntax error")
|
||||
})
|
||||
t.Run("will match plugin by glob", func(t *testing.T) {
|
||||
// given
|
||||
d := Discover{
|
||||
@@ -164,21 +137,6 @@ func TestMatchRepository(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, match)
|
||||
})
|
||||
t.Run("will throw an error for a bad pattern", func(t *testing.T) {
|
||||
// given
|
||||
d := Discover{
|
||||
Find: Find{
|
||||
Glob: "does-not-exist",
|
||||
},
|
||||
}
|
||||
f := setup(t, withDiscover(d))
|
||||
|
||||
// when
|
||||
_, err := f.service.matchRepository(context.Background(), f.path, f.env)
|
||||
|
||||
// then
|
||||
assert.ErrorContains(t, err, "error finding glob match for pattern")
|
||||
})
|
||||
t.Run("will match plugin by command when returns any output", func(t *testing.T) {
|
||||
// given
|
||||
d := Discover{
|
||||
@@ -281,49 +239,17 @@ func Test_Negative_ConfigFile_DoesnotExist(t *testing.T) {
|
||||
|
||||
func TestGenerateManifest(t *testing.T) {
|
||||
configFilePath := "./testdata/kustomize/config"
|
||||
|
||||
t.Run("successful generate", func(t *testing.T) {
|
||||
service, err := newService(configFilePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
res1, err := service.generateManifest(context.Background(), "testdata/kustomize", nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res1)
|
||||
|
||||
expectedOutput := "{\"apiVersion\":\"v1\",\"data\":{\"foo\":\"bar\"},\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"my-map\"}}"
|
||||
if res1 != nil {
|
||||
require.Equal(t, expectedOutput, res1.Manifests[0])
|
||||
}
|
||||
})
|
||||
t.Run("bad generate command", func(t *testing.T) {
|
||||
service, err := newService(configFilePath)
|
||||
require.NoError(t, err)
|
||||
service.WithGenerateCommand(Command{Command: []string{"bad-command"}})
|
||||
|
||||
res, err := service.generateManifest(context.Background(), "testdata/kustomize", nil)
|
||||
assert.ErrorContains(t, err, "executable file not found")
|
||||
assert.Nil(t, res.Manifests)
|
||||
})
|
||||
t.Run("bad yaml output", func(t *testing.T) {
|
||||
service, err := newService(configFilePath)
|
||||
require.NoError(t, err)
|
||||
service.WithGenerateCommand(Command{Command: []string{"echo", "invalid yaml: }"}})
|
||||
|
||||
res, err := service.generateManifest(context.Background(), "testdata/kustomize", nil)
|
||||
assert.ErrorContains(t, err, "failed to unmarshal manifest")
|
||||
assert.Nil(t, res.Manifests)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGenerateManifest_deadline_exceeded(t *testing.T) {
|
||||
configFilePath := "./testdata/kustomize/config"
|
||||
service, err := newService(configFilePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
expiredCtx, cancel := context.WithTimeout(context.Background(), time.Second*0)
|
||||
defer cancel()
|
||||
_, err = service.generateManifest(expiredCtx, "", nil)
|
||||
assert.ErrorContains(t, err, "context deadline exceeded")
|
||||
res1, err := service.generateManifest(context.Background(), "", nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res1)
|
||||
|
||||
expectedOutput := "{\"apiVersion\":\"v1\",\"data\":{\"foo\":\"bar\"},\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"my-map\"}}"
|
||||
if res1 != nil {
|
||||
require.Equal(t, expectedOutput, res1.Manifests[0])
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunCommandContextTimeout makes sure the command dies at timeout rather than sleeping past the timeout.
|
||||
@@ -342,387 +268,29 @@ func TestRunCommandContextTimeout(t *testing.T) {
|
||||
assert.Less(t, after.Sub(before), 1*time.Second)
|
||||
}
|
||||
|
||||
func TestRunCommandEmptyCommand(t *testing.T) {
|
||||
_, err := runCommand(context.Background(), Command{}, "", nil)
|
||||
assert.ErrorContains(t, err, "Command is empty")
|
||||
}
|
||||
|
||||
func Test_getParametersAnnouncement_empty_command(t *testing.T) {
|
||||
staticYAML := `
|
||||
- name: static-a
|
||||
- name: static-b
|
||||
`
|
||||
static := &[]*repoclient.ParameterAnnouncement{}
|
||||
err := yaml.Unmarshal([]byte(staticYAML), static)
|
||||
require.NoError(t, err)
|
||||
command := Command{
|
||||
Command: []string{"echo"},
|
||||
Args: []string{`[]`},
|
||||
func Test_getCommandArgsToLog(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no spaces",
|
||||
args: []string{"sh", "-c", "cat"},
|
||||
expected: "sh -c cat",
|
||||
},
|
||||
{
|
||||
name: "spaces",
|
||||
args: []string{"sh", "-c", `echo "hello world"`},
|
||||
expected: `sh -c "echo \"hello world\""`,
|
||||
},
|
||||
}
|
||||
res, err := getParametersAnnouncement(context.Background(), "", *static, command)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []*repoclient.ParameterAnnouncement{{Name: "static-a"}, {Name: "static-b"}}, res.ParameterAnnouncements)
|
||||
}
|
||||
|
||||
func Test_getParametersAnnouncement_no_command(t *testing.T) {
|
||||
staticYAML := `
|
||||
- name: static-a
|
||||
- name: static-b
|
||||
`
|
||||
static := &[]*repoclient.ParameterAnnouncement{}
|
||||
err := yaml.Unmarshal([]byte(staticYAML), static)
|
||||
require.NoError(t, err)
|
||||
command := Command{}
|
||||
res, err := getParametersAnnouncement(context.Background(), "", *static, command)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []*repoclient.ParameterAnnouncement{{Name: "static-a"}, {Name: "static-b"}}, res.ParameterAnnouncements)
|
||||
}
|
||||
|
||||
func Test_getParametersAnnouncement_static_and_dynamic(t *testing.T) {
|
||||
staticYAML := `
|
||||
- name: static-a
|
||||
- name: static-b
|
||||
`
|
||||
static := &[]*repoclient.ParameterAnnouncement{}
|
||||
err := yaml.Unmarshal([]byte(staticYAML), static)
|
||||
require.NoError(t, err)
|
||||
command := Command{
|
||||
Command: []string{"echo"},
|
||||
Args: []string{`[{"name": "dynamic-a"}, {"name": "dynamic-b"}]`},
|
||||
}
|
||||
res, err := getParametersAnnouncement(context.Background(), "", *static, command)
|
||||
require.NoError(t, err)
|
||||
expected := []*repoclient.ParameterAnnouncement{
|
||||
{Name: "dynamic-a"},
|
||||
{Name: "dynamic-b"},
|
||||
{Name: "static-a"},
|
||||
{Name: "static-b"},
|
||||
}
|
||||
assert.Equal(t, expected, res.ParameterAnnouncements)
|
||||
}
|
||||
|
||||
func Test_getParametersAnnouncement_invalid_json(t *testing.T) {
|
||||
command := Command{
|
||||
Command: []string{"echo"},
|
||||
Args: []string{`[`},
|
||||
}
|
||||
_, err := getParametersAnnouncement(context.Background(), "", []*repoclient.ParameterAnnouncement{}, command)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unexpected end of JSON input")
|
||||
}
|
||||
|
||||
func Test_getParametersAnnouncement_bad_command(t *testing.T) {
|
||||
command := Command{
|
||||
Command: []string{"exit"},
|
||||
Args: []string{"1"},
|
||||
}
|
||||
_, err := getParametersAnnouncement(context.Background(), "", []*repoclient.ParameterAnnouncement{}, command)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "error executing dynamic parameter output command")
|
||||
}
|
||||
|
||||
func Test_getTempDirMustCleanup(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
// Induce a directory create error to verify error handling.
|
||||
err := os.Chmod(tempDir, 0000)
|
||||
require.NoError(t, err)
|
||||
_, _, err = getTempDirMustCleanup(path.Join(tempDir, "test"))
|
||||
assert.ErrorContains(t, err, "error creating temp dir")
|
||||
|
||||
err = os.Chmod(tempDir, 0700)
|
||||
require.NoError(t, err)
|
||||
workDir, cleanup, err := getTempDirMustCleanup(tempDir)
|
||||
require.NoError(t, err)
|
||||
require.DirExists(t, workDir)
|
||||
cleanup()
|
||||
assert.NoDirExists(t, workDir)
|
||||
}
|
||||
|
||||
func TestService_Init(t *testing.T) {
|
||||
// Set up a base directory containing a test directory and a test file.
|
||||
tempDir := t.TempDir()
|
||||
workDir := path.Join(tempDir, "workDir")
|
||||
err := os.MkdirAll(workDir, 0700)
|
||||
require.NoError(t, err)
|
||||
testfile := path.Join(workDir, "testfile")
|
||||
file, err := os.Create(testfile)
|
||||
require.NoError(t, err)
|
||||
err = file.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Make the base directory read-only so Init's cleanup fails.
|
||||
err = os.Chmod(tempDir, 0000)
|
||||
require.NoError(t, err)
|
||||
s := NewService(CMPServerInitConstants{PluginConfig: PluginConfig{}})
|
||||
err = s.Init(workDir)
|
||||
assert.ErrorContains(t, err, "error removing workdir", "Init must throw an error if it can't remove the work directory")
|
||||
|
||||
// Make the base directory writable so Init's cleanup succeeds.
|
||||
err = os.Chmod(tempDir, 0700)
|
||||
require.NoError(t, err)
|
||||
err = s.Init(workDir)
|
||||
assert.NoError(t, err)
|
||||
assert.DirExists(t, workDir)
|
||||
assert.NoFileExists(t, testfile)
|
||||
}
|
||||
|
||||
func TestEnviron(t *testing.T) {
|
||||
t.Run("empty environ", func(t *testing.T) {
|
||||
env := environ([]*apiclient.EnvEntry{})
|
||||
assert.Nil(t, env)
|
||||
})
|
||||
t.Run("env vars with empty names or values", func(t *testing.T) {
|
||||
env := environ([]*apiclient.EnvEntry{
|
||||
{Value: "test"},
|
||||
{Name: "test"},
|
||||
for _, tc := range testCases {
|
||||
tcc := tc
|
||||
t.Run(tcc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert.Equal(t, tcc.expected, getCommandArgsToLog(exec.Command(tcc.args[0], tcc.args[1:]...)))
|
||||
})
|
||||
assert.Nil(t, env)
|
||||
})
|
||||
t.Run("proper env vars", func(t *testing.T) {
|
||||
env := environ([]*apiclient.EnvEntry{
|
||||
{Name: "name1", Value: "value1"},
|
||||
{Name: "name2", Value: "value2"},
|
||||
})
|
||||
assert.Equal(t, []string{"name1=value1", "name2=value2"}, env)
|
||||
})
|
||||
}
|
||||
|
||||
type MockGenerateManifestStream struct {
|
||||
metadataSent bool
|
||||
fileSent bool
|
||||
metadataRequest *apiclient.AppStreamRequest
|
||||
fileRequest *apiclient.AppStreamRequest
|
||||
response *apiclient.ManifestResponse
|
||||
}
|
||||
|
||||
func NewMockGenerateManifestStream(repoPath, appPath string, env []string) (*MockGenerateManifestStream, error) {
|
||||
tgz, mr, err := cmp.GetCompressedRepoAndMetadata(repoPath, appPath, env, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tgzstream.CloseAndDelete(tgz)
|
||||
|
||||
tgzBuffer := bytes.NewBuffer(nil)
|
||||
_, err = io.Copy(tgzBuffer, tgz)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to copy manifest targz to a byte buffer: %w", err)
|
||||
}
|
||||
|
||||
return &MockGenerateManifestStream{
|
||||
metadataRequest: mr,
|
||||
fileRequest: cmp.AppFileRequest(tgzBuffer.Bytes()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockGenerateManifestStream) SendAndClose(response *apiclient.ManifestResponse) error {
|
||||
m.response = response
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockGenerateManifestStream) Recv() (*apiclient.AppStreamRequest, error) {
|
||||
if !m.metadataSent {
|
||||
m.metadataSent = true
|
||||
return m.metadataRequest, nil
|
||||
}
|
||||
|
||||
if !m.fileSent {
|
||||
m.fileSent = true
|
||||
return m.fileRequest, nil
|
||||
}
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
func (m *MockGenerateManifestStream) Context() context.Context {
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
func TestService_GenerateManifest(t *testing.T) {
|
||||
configFilePath := "./testdata/kustomize/config"
|
||||
service, err := newService(configFilePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("successful generate", func(t *testing.T) {
|
||||
s, err := NewMockGenerateManifestStream("./testdata/kustomize", "./testdata/kustomize", nil)
|
||||
require.NoError(t, err)
|
||||
err = service.generateManifestGeneric(s)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, s.response)
|
||||
assert.Equal(t, []string{"{\"apiVersion\":\"v1\",\"data\":{\"foo\":\"bar\"},\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"my-map\"}}"}, s.response.Manifests)
|
||||
})
|
||||
|
||||
t.Run("out-of-bounds app path", func(t *testing.T) {
|
||||
s, err := NewMockGenerateManifestStream("./testdata/kustomize", "./testdata/kustomize", nil)
|
||||
require.NoError(t, err)
|
||||
// set a malicious app path on the metadata
|
||||
s.metadataRequest.Request.(*apiclient.AppStreamRequest_Metadata).Metadata.AppRelPath = "../out-of-bounds"
|
||||
err = service.generateManifestGeneric(s)
|
||||
require.ErrorContains(t, err, "illegal appPath")
|
||||
assert.Nil(t, s.response)
|
||||
})
|
||||
}
|
||||
|
||||
type MockMatchRepositoryStream struct {
|
||||
metadataSent bool
|
||||
fileSent bool
|
||||
metadataRequest *apiclient.AppStreamRequest
|
||||
fileRequest *apiclient.AppStreamRequest
|
||||
response *apiclient.RepositoryResponse
|
||||
}
|
||||
|
||||
func NewMockMatchRepositoryStream(repoPath, appPath string, env []string) (*MockMatchRepositoryStream, error) {
|
||||
tgz, mr, err := cmp.GetCompressedRepoAndMetadata(repoPath, appPath, env, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tgzstream.CloseAndDelete(tgz)
|
||||
|
||||
tgzBuffer := bytes.NewBuffer(nil)
|
||||
_, err = io.Copy(tgzBuffer, tgz)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to copy manifest targz to a byte buffer: %w", err)
|
||||
}
|
||||
|
||||
return &MockMatchRepositoryStream{
|
||||
metadataRequest: mr,
|
||||
fileRequest: cmp.AppFileRequest(tgzBuffer.Bytes()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockMatchRepositoryStream) SendAndClose(response *apiclient.RepositoryResponse) error {
|
||||
m.response = response
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockMatchRepositoryStream) Recv() (*apiclient.AppStreamRequest, error) {
|
||||
if !m.metadataSent {
|
||||
m.metadataSent = true
|
||||
return m.metadataRequest, nil
|
||||
}
|
||||
|
||||
if !m.fileSent {
|
||||
m.fileSent = true
|
||||
return m.fileRequest, nil
|
||||
}
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
func (m *MockMatchRepositoryStream) Context() context.Context {
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
func TestService_MatchRepository(t *testing.T) {
|
||||
configFilePath := "./testdata/kustomize/config"
|
||||
service, err := newService(configFilePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("supported app", func(t *testing.T) {
|
||||
s, err := NewMockMatchRepositoryStream("./testdata/kustomize", "./testdata/kustomize", nil)
|
||||
require.NoError(t, err)
|
||||
err = service.matchRepositoryGeneric(s)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, s.response)
|
||||
assert.True(t, s.response.IsSupported)
|
||||
})
|
||||
|
||||
t.Run("unsupported app", func(t *testing.T) {
|
||||
s, err := NewMockMatchRepositoryStream("./testdata/ksonnet", "./testdata/ksonnet", nil)
|
||||
require.NoError(t, err)
|
||||
err = service.matchRepositoryGeneric(s)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, s.response)
|
||||
assert.False(t, s.response.IsSupported)
|
||||
})
|
||||
}
|
||||
|
||||
type MockParametersAnnouncementStream struct {
|
||||
metadataSent bool
|
||||
fileSent bool
|
||||
metadataRequest *apiclient.AppStreamRequest
|
||||
fileRequest *apiclient.AppStreamRequest
|
||||
response *apiclient.ParametersAnnouncementResponse
|
||||
}
|
||||
|
||||
func NewMockParametersAnnouncementStream(repoPath, appPath string, env []string) (*MockParametersAnnouncementStream, error) {
|
||||
tgz, mr, err := cmp.GetCompressedRepoAndMetadata(repoPath, appPath, env, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tgzstream.CloseAndDelete(tgz)
|
||||
|
||||
tgzBuffer := bytes.NewBuffer(nil)
|
||||
_, err = io.Copy(tgzBuffer, tgz)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to copy manifest targz to a byte buffer: %w", err)
|
||||
}
|
||||
|
||||
return &MockParametersAnnouncementStream{
|
||||
metadataRequest: mr,
|
||||
fileRequest: cmp.AppFileRequest(tgzBuffer.Bytes()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockParametersAnnouncementStream) SendAndClose(response *apiclient.ParametersAnnouncementResponse) error {
|
||||
m.response = response
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockParametersAnnouncementStream) Recv() (*apiclient.AppStreamRequest, error) {
|
||||
if !m.metadataSent {
|
||||
m.metadataSent = true
|
||||
return m.metadataRequest, nil
|
||||
}
|
||||
|
||||
if !m.fileSent {
|
||||
m.fileSent = true
|
||||
return m.fileRequest, nil
|
||||
}
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
func (m *MockParametersAnnouncementStream) SetHeader(metadata.MD) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockParametersAnnouncementStream) SendHeader(metadata.MD) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockParametersAnnouncementStream) SetTrailer(metadata.MD) {}
|
||||
|
||||
func (m *MockParametersAnnouncementStream) Context() context.Context {
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
func (m *MockParametersAnnouncementStream) SendMsg(interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockParametersAnnouncementStream) RecvMsg(interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestService_GetParametersAnnouncement(t *testing.T) {
|
||||
configFilePath := "./testdata/kustomize/config"
|
||||
service, err := newService(configFilePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("successful response", func(t *testing.T) {
|
||||
s, err := NewMockParametersAnnouncementStream("./testdata/kustomize", "./testdata/kustomize", nil)
|
||||
require.NoError(t, err)
|
||||
err = service.GetParametersAnnouncement(s)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, s.response)
|
||||
require.Len(t, s.response.ParameterAnnouncements, 1)
|
||||
assert.Equal(t, repoclient.ParameterAnnouncement{Name: "test-param", String_: "test-value"}, *s.response.ParameterAnnouncements[0])
|
||||
})
|
||||
t.Run("out of bounds app", func(t *testing.T) {
|
||||
s, err := NewMockParametersAnnouncementStream("./testdata/kustomize", "./testdata/kustomize", nil)
|
||||
require.NoError(t, err)
|
||||
// set a malicious app path on the metadata
|
||||
s.metadataRequest.Request.(*apiclient.AppStreamRequest_Metadata).Metadata.AppRelPath = "../out-of-bounds"
|
||||
err = service.GetParametersAnnouncement(s)
|
||||
require.ErrorContains(t, err, "illegal appPath")
|
||||
require.Nil(t, s.response)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,12 +7,8 @@ spec:
|
||||
init:
|
||||
command: [kustomize, version]
|
||||
generate:
|
||||
command: [sh, -c, "kustomize build"]
|
||||
command: [sh, -c, "cd testdata/kustomize && kustomize build"]
|
||||
discover:
|
||||
find:
|
||||
command: [sh, -c, find . -name kustomization.yaml]
|
||||
glob: "**/kustomization.yaml"
|
||||
parameters:
|
||||
static:
|
||||
- name: test-param
|
||||
string: test-value
|
||||
glob: "**/*/kustomization.yaml"
|
||||
|
||||
@@ -108,7 +108,7 @@ func (a *ArgoCDCMPServer) CreateGRPC() (*grpc.Server, error) {
|
||||
return true, nil
|
||||
}))
|
||||
pluginService := plugin.NewService(a.initConstants)
|
||||
err := pluginService.Init(common.GetCMPWorkDir())
|
||||
err := pluginService.Init()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error initializing plugin service: %s", err)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Default service addresses and URLS of Argo CD internal services
|
||||
@@ -316,3 +319,10 @@ const (
|
||||
SecurityMedium = 2 // Could indicate malicious events, but has a high likelihood of being user/system error (i.e. access denied)
|
||||
SecurityLow = 1 // Unexceptional entries (i.e. successful access logs)
|
||||
)
|
||||
|
||||
// Common error messages
|
||||
const TokenVerificationError = "failed to verify the token"
|
||||
|
||||
var TokenVerificationErr = errors.New(TokenVerificationError)
|
||||
|
||||
var PermissionDeniedAPIError = status.Error(codes.PermissionDenied, "permission denied")
|
||||
|
||||
@@ -335,7 +335,7 @@ func (ctrl *ApplicationController) handleObjectUpdated(managedByApp map[string]b
|
||||
}
|
||||
|
||||
if !ctrl.canProcessApp(obj) {
|
||||
// Don't force refresh app if app belongs to a different controller shard
|
||||
// Don't force refresh app if app belongs to a different controller shard or is outside the allowed namespaces.
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -907,7 +907,7 @@ func (ctrl *ApplicationController) processProjectQueueItem() (processNext bool)
|
||||
func (ctrl *ApplicationController) finalizeProjectDeletion(proj *appv1.AppProject) error {
|
||||
apps, err := ctrl.appLister.Applications(ctrl.namespace).List(labels.Everything())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing applications: %w", err)
|
||||
return err
|
||||
}
|
||||
appsCount := 0
|
||||
for i := range apps {
|
||||
@@ -1077,7 +1077,7 @@ func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Applic
|
||||
func (ctrl *ApplicationController) removeCascadeFinalizer(app *appv1.Application) error {
|
||||
_, err := ctrl.getAppProj(app)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting project: %w", err)
|
||||
return err
|
||||
}
|
||||
app.UnSetCascadedDeletion()
|
||||
var patch []byte
|
||||
@@ -1256,12 +1256,12 @@ func (ctrl *ApplicationController) setOperationState(app *appv1.Application, sta
|
||||
}
|
||||
patchJSON, err := json.Marshal(patch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling json: %w", err)
|
||||
return err
|
||||
}
|
||||
if app.Status.OperationState != nil && app.Status.OperationState.FinishedAt != nil && state.FinishedAt == nil {
|
||||
patchJSON, err = jsonpatch.MergeMergePatches(patchJSON, []byte(`{"status": {"operationState": {"finishedAt": null}}}`))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error merging operation state patch: %w", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1272,7 +1272,7 @@ func (ctrl *ApplicationController) setOperationState(app *appv1.Application, sta
|
||||
if apierr.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("error patching application with operation state: %w", err)
|
||||
return err
|
||||
}
|
||||
log.Infof("updated '%s' operation (phase: %s)", app.QualifiedName(), state.Phase)
|
||||
if state.Phase.Completed() {
|
||||
@@ -1719,6 +1719,13 @@ func (ctrl *ApplicationController) canProcessApp(obj interface{}) bool {
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// Only process given app if it exists in a watched namespace, or in the
|
||||
// control plane's namespace.
|
||||
if app.Namespace != ctrl.namespace && !glob.MatchStringInList(ctrl.applicationNamespaces, app.Namespace, false) {
|
||||
return false
|
||||
}
|
||||
|
||||
if ctrl.clusterFilter != nil {
|
||||
cluster, err := ctrl.db.GetCluster(context.Background(), app.Spec.Destination.Server)
|
||||
if err != nil {
|
||||
@@ -1727,12 +1734,6 @@ func (ctrl *ApplicationController) canProcessApp(obj interface{}) bool {
|
||||
return ctrl.clusterFilter(cluster)
|
||||
}
|
||||
|
||||
// Only process given app if it exists in a watched namespace, or in the
|
||||
// control plane's namespace.
|
||||
if app.Namespace != ctrl.namespace && !glob.MatchStringInList(ctrl.applicationNamespaces, app.Namespace, false) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1373,3 +1373,31 @@ func TestToAppKey(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_canProcessApp(t *testing.T) {
|
||||
app := newFakeApp()
|
||||
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}})
|
||||
ctrl.applicationNamespaces = []string{"good"}
|
||||
t.Run("without cluster filter, good namespace", func(t *testing.T) {
|
||||
app.Namespace = "good"
|
||||
canProcess := ctrl.canProcessApp(app)
|
||||
assert.True(t, canProcess)
|
||||
})
|
||||
t.Run("without cluster filter, bad namespace", func(t *testing.T) {
|
||||
app.Namespace = "bad"
|
||||
canProcess := ctrl.canProcessApp(app)
|
||||
assert.False(t, canProcess)
|
||||
})
|
||||
t.Run("with cluster filter, good namespace", func(t *testing.T) {
|
||||
app.Namespace = "good"
|
||||
ctrl.clusterFilter = func(_ *argoappv1.Cluster) bool { return true }
|
||||
canProcess := ctrl.canProcessApp(app)
|
||||
assert.True(t, canProcess)
|
||||
})
|
||||
t.Run("with cluster filter, bad namespace", func(t *testing.T) {
|
||||
app.Namespace = "bad"
|
||||
ctrl.clusterFilter = func(_ *argoappv1.Cluster) bool { return true }
|
||||
canProcess := ctrl.canProcessApp(app)
|
||||
assert.False(t, canProcess)
|
||||
})
|
||||
}
|
||||
|
||||
41
controller/cache/cache.go
vendored
41
controller/cache/cache.go
vendored
@@ -25,6 +25,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/controller/metrics"
|
||||
@@ -382,16 +383,25 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
|
||||
|
||||
cluster, err := c.db.GetCluster(context.Background(), server)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting cluster: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !c.canHandleCluster(cluster) {
|
||||
return nil, fmt.Errorf("controller is configured to ignore cluster %s", cluster.Server)
|
||||
}
|
||||
|
||||
resourceCustomLabels, err := c.settingsMgr.GetResourceCustomLabels()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting custom label: %w", err)
|
||||
clusterCacheConfig := cluster.RESTConfig()
|
||||
// Controller dynamically fetches all resource types available on the cluster
|
||||
// using a discovery API that may contain deprecated APIs.
|
||||
// This causes log flooding when managing a large number of clusters.
|
||||
// https://github.com/argoproj/argo-cd/issues/11973
|
||||
// However, we can safely suppress deprecation warnings
|
||||
// because we do not rely on resources with a particular API group or version.
|
||||
// https://kubernetes.io/blog/2020/09/03/warnings/#customize-client-handling
|
||||
//
|
||||
// Completely suppress warning logs only for log levels that are less than Debug.
|
||||
if log.GetLevel() < log.DebugLevel {
|
||||
clusterCacheConfig.WarningHandler = rest.NoWarnings{}
|
||||
}
|
||||
|
||||
clusterCacheOpts := []clustercache.UpdateSettingsFunc{
|
||||
@@ -405,7 +415,7 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
|
||||
clustercache.SetClusterResources(cluster.ClusterResources),
|
||||
clustercache.SetPopulateResourceInfoHandler(func(un *unstructured.Unstructured, isRoot bool) (interface{}, bool) {
|
||||
res := &ResourceInfo{}
|
||||
populateNodeInfo(un, res, resourceCustomLabels)
|
||||
populateNodeInfo(un, res)
|
||||
c.lock.RLock()
|
||||
cacheSettings := c.cacheSettings
|
||||
c.lock.RUnlock()
|
||||
@@ -425,7 +435,7 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
|
||||
clustercache.SetRetryOptions(clusterCacheAttemptLimit, clusterCacheRetryUseBackoff, isRetryableError),
|
||||
}
|
||||
|
||||
clusterCache = clustercache.NewClusterCache(cluster.RESTConfig(), clusterCacheOpts...)
|
||||
clusterCache = clustercache.NewClusterCache(clusterCacheConfig, clusterCacheOpts...)
|
||||
|
||||
_ = clusterCache.OnResourceUpdated(func(newRes *clustercache.Resource, oldRes *clustercache.Resource, namespaceResources map[kube.ResourceKey]*clustercache.Resource) {
|
||||
toNotify := make(map[string]bool)
|
||||
@@ -461,11 +471,11 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
|
||||
func (c *liveStateCache) getSyncedCluster(server string) (clustercache.ClusterCache, error) {
|
||||
clusterCache, err := c.getCluster(server)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting cluster: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
err = clusterCache.EnsureSynced()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error synchronizing cache state : %w", err)
|
||||
return nil, err
|
||||
}
|
||||
return clusterCache, nil
|
||||
}
|
||||
@@ -473,10 +483,11 @@ func (c *liveStateCache) getSyncedCluster(server string) (clustercache.ClusterCa
|
||||
func (c *liveStateCache) invalidate(cacheSettings cacheSettings) {
|
||||
log.Info("invalidating live state cache")
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
c.cacheSettings = cacheSettings
|
||||
for _, clust := range c.clusters {
|
||||
clusters := c.clusters
|
||||
c.lock.Unlock()
|
||||
|
||||
for _, clust := range clusters {
|
||||
clust.Invalidate(clustercache.SetSettings(cacheSettings.clusterSettings))
|
||||
}
|
||||
log.Info("live state cache invalidated")
|
||||
@@ -599,7 +610,7 @@ func (c *liveStateCache) watchSettings(ctx context.Context) {
|
||||
func (c *liveStateCache) Init() error {
|
||||
cacheSettings, err := c.loadCacheSettings()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading cache settings: %w", err)
|
||||
return err
|
||||
}
|
||||
c.cacheSettings = *cacheSettings
|
||||
return nil
|
||||
@@ -686,12 +697,14 @@ func (c *liveStateCache) handleModEvent(oldCluster *appv1.Cluster, newCluster *a
|
||||
}
|
||||
|
||||
func (c *liveStateCache) handleDeleteEvent(clusterServer string) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.lock.RLock()
|
||||
cluster, ok := c.clusters[clusterServer]
|
||||
c.lock.RUnlock()
|
||||
if ok {
|
||||
cluster.Invalidate()
|
||||
c.lock.Lock()
|
||||
delete(c.clusters, clusterServer)
|
||||
c.lock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
97
controller/cache/cache_test.go
vendored
97
controller/cache/cache_test.go
vendored
@@ -1,10 +1,13 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
apierr "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -14,8 +17,10 @@ import (
|
||||
"github.com/argoproj/gitops-engine/pkg/cache"
|
||||
"github.com/argoproj/gitops-engine/pkg/cache/mocks"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
|
||||
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
argosettings "github.com/argoproj/argo-cd/v2/util/settings"
|
||||
)
|
||||
|
||||
type netError string
|
||||
@@ -106,6 +111,98 @@ func TestHandleAddEvent_ClusterExcluded(t *testing.T) {
|
||||
assert.Len(t, clustersCache.clusters, 0)
|
||||
}
|
||||
|
||||
func TestHandleDeleteEvent_CacheDeadlock(t *testing.T) {
|
||||
testCluster := &appv1.Cluster{
|
||||
Server: "https://mycluster",
|
||||
Config: appv1.ClusterConfig{Username: "bar"},
|
||||
}
|
||||
fakeClient := fake.NewSimpleClientset()
|
||||
settingsMgr := argosettings.NewSettingsManager(context.TODO(), fakeClient, "argocd")
|
||||
externalLockRef := sync.RWMutex{}
|
||||
gitopsEngineClusterCache := &mocks.ClusterCache{}
|
||||
clustersCache := liveStateCache{
|
||||
clusters: map[string]cache.ClusterCache{
|
||||
testCluster.Server: gitopsEngineClusterCache,
|
||||
},
|
||||
clusterFilter: func(cluster *appv1.Cluster) bool {
|
||||
return true
|
||||
},
|
||||
settingsMgr: settingsMgr,
|
||||
// Set the lock here so we can reference it later
|
||||
// nolint We need to overwrite here to have access to the lock
|
||||
lock: externalLockRef,
|
||||
}
|
||||
channel := make(chan string)
|
||||
// Mocked lock held by the gitops-engine cluster cache
|
||||
mockMutex := sync.RWMutex{}
|
||||
// Locks to force trigger condition during test
|
||||
// Condition order:
|
||||
// EnsuredSynced -> Locks gitops-engine
|
||||
// handleDeleteEvent -> Locks liveStateCache
|
||||
// EnsureSynced via sync, newResource, populateResourceInfoHandler -> attempts to Lock liveStateCache
|
||||
// handleDeleteEvent via cluster.Invalidate -> attempts to Lock gitops-engine
|
||||
handleDeleteWasCalled := sync.Mutex{}
|
||||
engineHoldsLock := sync.Mutex{}
|
||||
handleDeleteWasCalled.Lock()
|
||||
engineHoldsLock.Lock()
|
||||
gitopsEngineClusterCache.On("EnsureSynced").Run(func(args mock.Arguments) {
|
||||
// Held by EnsureSync calling into sync and watchEvents
|
||||
mockMutex.Lock()
|
||||
defer mockMutex.Unlock()
|
||||
// Continue Execution of timer func
|
||||
engineHoldsLock.Unlock()
|
||||
// Wait for handleDeleteEvent to be called triggering the lock
|
||||
// on the liveStateCache
|
||||
handleDeleteWasCalled.Lock()
|
||||
t.Logf("handleDelete was called, EnsureSynced continuing...")
|
||||
handleDeleteWasCalled.Unlock()
|
||||
// Try and obtain the lock on the liveStateCache
|
||||
alreadyFailed := !externalLockRef.TryLock()
|
||||
if alreadyFailed {
|
||||
channel <- "DEADLOCKED -- EnsureSynced could not obtain lock on liveStateCache"
|
||||
return
|
||||
}
|
||||
externalLockRef.Lock()
|
||||
t.Logf("EnsureSynce was able to lock liveStateCache")
|
||||
externalLockRef.Unlock()
|
||||
}).Return(nil).Once()
|
||||
gitopsEngineClusterCache.On("Invalidate").Run(func(args mock.Arguments) {
|
||||
// If deadlock is fixed should be able to acquire lock here
|
||||
alreadyFailed := !mockMutex.TryLock()
|
||||
if alreadyFailed {
|
||||
channel <- "DEADLOCKED -- Invalidate could not obtain lock on gitops-engine"
|
||||
return
|
||||
}
|
||||
mockMutex.Lock()
|
||||
t.Logf("Invalidate was able to lock gitops-engine cache")
|
||||
mockMutex.Unlock()
|
||||
}).Return()
|
||||
go func() {
|
||||
// Start the gitops-engine lock holds
|
||||
go func() {
|
||||
err := gitopsEngineClusterCache.EnsureSynced()
|
||||
if err != nil {
|
||||
assert.Fail(t, err.Error())
|
||||
}
|
||||
}()
|
||||
// Wait for EnsureSynced to grab the lock for gitops-engine
|
||||
engineHoldsLock.Lock()
|
||||
t.Log("EnsureSynced has obtained lock on gitops-engine")
|
||||
engineHoldsLock.Unlock()
|
||||
// Run in background
|
||||
go clustersCache.handleDeleteEvent(testCluster.Server)
|
||||
// Allow execution to continue on clusters cache call to trigger lock
|
||||
handleDeleteWasCalled.Unlock()
|
||||
channel <- "PASSED"
|
||||
}()
|
||||
select {
|
||||
case str := <-channel:
|
||||
assert.Equal(t, "PASSED", str, str)
|
||||
case <-time.After(5 * time.Second):
|
||||
assert.Fail(t, "Ended up in deadlock")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRetryableError(t *testing.T) {
|
||||
var (
|
||||
tlsHandshakeTimeoutErr net.Error = netError("net/http: TLS handshake timeout")
|
||||
|
||||
11
controller/cache/info.go
vendored
11
controller/cache/info.go
vendored
@@ -19,21 +19,12 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/util/resource"
|
||||
)
|
||||
|
||||
func populateNodeInfo(un *unstructured.Unstructured, res *ResourceInfo, customLabels []string) {
|
||||
func populateNodeInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
gvk := un.GroupVersionKind()
|
||||
revision := resource.GetRevision(un)
|
||||
if revision > 0 {
|
||||
res.Info = append(res.Info, v1alpha1.InfoItem{Name: "Revision", Value: fmt.Sprintf("Rev:%v", revision)})
|
||||
}
|
||||
if len(customLabels) > 0 {
|
||||
if labels := un.GetLabels(); labels != nil {
|
||||
for _, customLabel := range customLabels {
|
||||
if value, ok := labels[customLabel]; ok {
|
||||
res.Info = append(res.Info, v1alpha1.InfoItem{Name: customLabel, Value: value})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
switch gvk.Group {
|
||||
case "":
|
||||
switch gvk.Kind {
|
||||
|
||||
76
controller/cache/info_test.go
vendored
76
controller/cache/info_test.go
vendored
@@ -271,7 +271,7 @@ func TestGetPodInfo(t *testing.T) {
|
||||
`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(pod, info, []string{})
|
||||
populateNodeInfo(pod, info)
|
||||
assert.Equal(t, []v1alpha1.InfoItem{
|
||||
{Name: "Node", Value: "minikube"},
|
||||
{Name: "Containers", Value: "0/1"},
|
||||
@@ -302,7 +302,7 @@ status:
|
||||
`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(node, info, []string{})
|
||||
populateNodeInfo(node, info)
|
||||
assert.Equal(t, &NodeInfo{
|
||||
Name: "minikube",
|
||||
Capacity: v1.ResourceList{v1.ResourceMemory: resource.MustParse("6091320Ki"), v1.ResourceCPU: resource.MustParse("6")},
|
||||
@@ -312,7 +312,7 @@ status:
|
||||
|
||||
func TestGetServiceInfo(t *testing.T) {
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(testService, info, []string{})
|
||||
populateNodeInfo(testService, info)
|
||||
assert.Equal(t, 0, len(info.Info))
|
||||
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
|
||||
TargetLabels: map[string]string{"app": "guestbook"},
|
||||
@@ -322,7 +322,7 @@ func TestGetServiceInfo(t *testing.T) {
|
||||
|
||||
func TestGetLinkAnnotatedServiceInfo(t *testing.T) {
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(testLinkAnnotatedService, info, []string{})
|
||||
populateNodeInfo(testLinkAnnotatedService, info)
|
||||
assert.Equal(t, 0, len(info.Info))
|
||||
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
|
||||
TargetLabels: map[string]string{"app": "guestbook"},
|
||||
@@ -333,7 +333,7 @@ func TestGetLinkAnnotatedServiceInfo(t *testing.T) {
|
||||
|
||||
func TestGetIstioVirtualServiceInfo(t *testing.T) {
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(testIstioVirtualService, info, []string{})
|
||||
populateNodeInfo(testIstioVirtualService, info)
|
||||
assert.Equal(t, 0, len(info.Info))
|
||||
require.NotNil(t, info.NetworkingInfo)
|
||||
require.NotNil(t, info.NetworkingInfo.TargetRefs)
|
||||
@@ -363,7 +363,7 @@ func TestGetIngressInfo(t *testing.T) {
|
||||
}
|
||||
for _, tc := range tests {
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(tc.Ingress, info, []string{})
|
||||
populateNodeInfo(tc.Ingress, info)
|
||||
assert.Equal(t, 0, len(info.Info))
|
||||
sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
|
||||
return strings.Compare(info.NetworkingInfo.TargetRefs[j].Name, info.NetworkingInfo.TargetRefs[i].Name) < 0
|
||||
@@ -388,7 +388,7 @@ func TestGetIngressInfo(t *testing.T) {
|
||||
|
||||
func TestGetLinkAnnotatedIngressInfo(t *testing.T) {
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(testLinkAnnotatedIngress, info, []string{})
|
||||
populateNodeInfo(testLinkAnnotatedIngress, info)
|
||||
assert.Equal(t, 0, len(info.Info))
|
||||
sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
|
||||
return strings.Compare(info.NetworkingInfo.TargetRefs[j].Name, info.NetworkingInfo.TargetRefs[i].Name) < 0
|
||||
@@ -412,7 +412,7 @@ func TestGetLinkAnnotatedIngressInfo(t *testing.T) {
|
||||
|
||||
func TestGetIngressInfoWildCardPath(t *testing.T) {
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(testIngressWildCardPath, info, []string{})
|
||||
populateNodeInfo(testIngressWildCardPath, info)
|
||||
assert.Equal(t, 0, len(info.Info))
|
||||
sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
|
||||
return strings.Compare(info.NetworkingInfo.TargetRefs[j].Name, info.NetworkingInfo.TargetRefs[i].Name) < 0
|
||||
@@ -436,7 +436,7 @@ func TestGetIngressInfoWildCardPath(t *testing.T) {
|
||||
|
||||
func TestGetIngressInfoWithoutTls(t *testing.T) {
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(testIngressWithoutTls, info, []string{})
|
||||
populateNodeInfo(testIngressWithoutTls, info)
|
||||
assert.Equal(t, 0, len(info.Info))
|
||||
sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
|
||||
return strings.Compare(info.NetworkingInfo.TargetRefs[j].Name, info.NetworkingInfo.TargetRefs[i].Name) < 0
|
||||
@@ -481,7 +481,7 @@ func TestGetIngressInfoWithHost(t *testing.T) {
|
||||
- ip: 107.178.210.11`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(ingress, info, []string{})
|
||||
populateNodeInfo(ingress, info)
|
||||
|
||||
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
|
||||
Ingress: []v1.LoadBalancerIngress{{IP: "107.178.210.11"}},
|
||||
@@ -514,7 +514,7 @@ func TestGetIngressInfoNoHost(t *testing.T) {
|
||||
`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(ingress, info, []string{})
|
||||
populateNodeInfo(ingress, info)
|
||||
|
||||
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
|
||||
TargetRefs: []v1alpha1.ResourceRef{{
|
||||
@@ -549,7 +549,7 @@ func TestExternalUrlWithSubPath(t *testing.T) {
|
||||
- ip: 107.178.210.11`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(ingress, info, []string{})
|
||||
populateNodeInfo(ingress, info)
|
||||
|
||||
expectedExternalUrls := []string{"https://107.178.210.11/my/sub/path/"}
|
||||
assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs)
|
||||
@@ -585,7 +585,7 @@ func TestExternalUrlWithMultipleSubPaths(t *testing.T) {
|
||||
- ip: 107.178.210.11`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(ingress, info, []string{})
|
||||
populateNodeInfo(ingress, info)
|
||||
|
||||
expectedExternalUrls := []string{"https://helm-guestbook.com/my/sub/path/", "https://helm-guestbook.com/my/sub/path/2", "https://helm-guestbook.com"}
|
||||
actualURLs := info.NetworkingInfo.ExternalURLs
|
||||
@@ -615,7 +615,7 @@ func TestExternalUrlWithNoSubPath(t *testing.T) {
|
||||
- ip: 107.178.210.11`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(ingress, info, []string{})
|
||||
populateNodeInfo(ingress, info)
|
||||
|
||||
expectedExternalUrls := []string{"https://107.178.210.11"}
|
||||
assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs)
|
||||
@@ -643,54 +643,8 @@ func TestExternalUrlWithNetworkingApi(t *testing.T) {
|
||||
- ip: 107.178.210.11`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(ingress, info, []string{})
|
||||
populateNodeInfo(ingress, info)
|
||||
|
||||
expectedExternalUrls := []string{"https://107.178.210.11"}
|
||||
assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs)
|
||||
}
|
||||
|
||||
func TestCustomLabel(t *testing.T) {
|
||||
configmap := strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(configmap, info, []string{"my-label"})
|
||||
|
||||
assert.Equal(t, 0, len(info.Info))
|
||||
|
||||
configmap = strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm
|
||||
labels:
|
||||
my-label: value`)
|
||||
|
||||
info = &ResourceInfo{}
|
||||
populateNodeInfo(configmap, info, []string{"my-label", "other-label"})
|
||||
|
||||
assert.Equal(t, 1, len(info.Info))
|
||||
assert.Equal(t, "my-label", info.Info[0].Name)
|
||||
assert.Equal(t, "value", info.Info[0].Value)
|
||||
|
||||
configmap = strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm
|
||||
labels:
|
||||
my-label: value
|
||||
other-label: value2`)
|
||||
|
||||
info = &ResourceInfo{}
|
||||
populateNodeInfo(configmap, info, []string{"my-label", "other-label"})
|
||||
|
||||
assert.Equal(t, 2, len(info.Info))
|
||||
assert.Equal(t, "my-label", info.Info[0].Name)
|
||||
assert.Equal(t, "value", info.Info[0].Value)
|
||||
assert.Equal(t, "other-label", info.Info[1].Name)
|
||||
assert.Equal(t, "value2", info.Info[1].Value)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package controller
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"fmt"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/cache"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -93,7 +93,7 @@ func (c *clusterInfoUpdater) updateClusters() {
|
||||
func (c *clusterInfoUpdater) updateClusterInfo(cluster appv1.Cluster, info *cache.ClusterInfo) error {
|
||||
apps, err := c.appLister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while fetching the apps list: %w", err)
|
||||
return err
|
||||
}
|
||||
var appCount int64
|
||||
for _, a := range apps {
|
||||
|
||||
@@ -41,30 +41,27 @@ func setApplicationHealth(resources []managedResource, statuses []appv1.Resource
|
||||
savedErr = err
|
||||
}
|
||||
}
|
||||
if healthStatus != nil {
|
||||
if persistResourceHealth {
|
||||
resHealth := appv1.HealthStatus{Status: healthStatus.Status, Message: healthStatus.Message}
|
||||
statuses[i].Health = &resHealth
|
||||
} else {
|
||||
statuses[i].Health = nil
|
||||
}
|
||||
|
||||
if healthStatus == nil {
|
||||
continue
|
||||
}
|
||||
// Is health status is missing but resource has not built-in/custom health check then it should not affect parent app health
|
||||
if _, hasOverride := healthOverrides[lua.GetConfigMapKey(gvk)]; healthStatus.Status == health.HealthStatusMissing && !hasOverride && health.GetHealthCheckFunc(gvk) == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if persistResourceHealth {
|
||||
resHealth := appv1.HealthStatus{Status: healthStatus.Status, Message: healthStatus.Message}
|
||||
statuses[i].Health = &resHealth
|
||||
} else {
|
||||
statuses[i].Health = nil
|
||||
}
|
||||
// Missing or Unknown health status of child Argo CD app should not affect parent
|
||||
if res.Kind == application.ApplicationKind && res.Group == application.Group && (healthStatus.Status == health.HealthStatusMissing || healthStatus.Status == health.HealthStatusUnknown) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Is health status is missing but resource has not built-in/custom health check then it should not affect parent app health
|
||||
if _, hasOverride := healthOverrides[lua.GetConfigMapKey(gvk)]; healthStatus.Status == health.HealthStatusMissing && !hasOverride && health.GetHealthCheckFunc(gvk) == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Missing or Unknown health status of child Argo CD app should not affect parent
|
||||
if res.Kind == application.ApplicationKind && res.Group == application.Group && (healthStatus.Status == health.HealthStatusMissing || healthStatus.Status == health.HealthStatusUnknown) {
|
||||
continue
|
||||
}
|
||||
|
||||
if health.IsWorse(appHealth.Status, healthStatus.Status) {
|
||||
appHealth.Status = healthStatus.Status
|
||||
if health.IsWorse(appHealth.Status, healthStatus.Status) {
|
||||
appHealth.Status = healthStatus.Status
|
||||
}
|
||||
}
|
||||
}
|
||||
if persistResourceHealth {
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/argoproj/gitops-engine/pkg/health"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/robfig/cron"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
|
||||
@@ -193,10 +193,7 @@ func NewMetricsServer(addr string, appLister applister.ApplicationLister, appFil
|
||||
redisRequestCounter: redisRequestCounter,
|
||||
redisRequestHistogram: redisRequestHistogram,
|
||||
hostname: hostname,
|
||||
// This cron is used to expire the metrics cache.
|
||||
// Currently clearing the metrics cache is logging and deleting from the map
|
||||
// so there is no possibility of panic, but we will add a chain to keep robfig/cron v1 behavior.
|
||||
cron: cron.New(cron.WithChain(cron.Recover(cron.PrintfLogger(log.StandardLogger())))),
|
||||
cron: cron.New(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -284,7 +281,7 @@ func (m *MetricsServer) SetExpiration(cacheExpiration time.Duration) error {
|
||||
return errors.New("Expiration is already set")
|
||||
}
|
||||
|
||||
_, err := m.cron.AddFunc(fmt.Sprintf("@every %s", cacheExpiration), func() {
|
||||
err := m.cron.AddFunc(fmt.Sprintf("@every %s", cacheExpiration), func() {
|
||||
log.Infof("Reset Prometheus metrics based on existing expiration '%v'", cacheExpiration)
|
||||
m.syncCounter.Reset()
|
||||
m.kubectlExecCounter.Reset()
|
||||
|
||||
@@ -659,7 +659,7 @@ func (m *appStateManager) persistRevisionHistory(app *v1alpha1.Application, revi
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling revision history patch: %w", err)
|
||||
return err
|
||||
}
|
||||
_, err = m.appclientset.ArgoprojV1alpha1().Applications(app.Namespace).Patch(context.Background(), app.Name, types.MergePatchType, patch, metav1.PatchOptions{})
|
||||
return err
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
cdcommon "github.com/argoproj/argo-cd/v2/common"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
@@ -21,6 +20,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/managedfields"
|
||||
"k8s.io/kubectl/pkg/util/openapi"
|
||||
|
||||
cdcommon "github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/controller/metrics"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
listersv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
|
||||
@@ -212,7 +212,14 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
|
||||
}
|
||||
trackingMethod := argo.GetTrackingMethod(m.settingsMgr)
|
||||
|
||||
opts := []sync.SyncOpt{
|
||||
syncCtx, cleanup, err := sync.NewSyncContext(
|
||||
compareResult.syncStatus.Revision,
|
||||
reconciliationResult,
|
||||
restConfig,
|
||||
rawConfig,
|
||||
m.kubectl,
|
||||
app.Spec.Destination.Namespace,
|
||||
openAPISchema,
|
||||
sync.WithLogr(logutils.NewLogrusLogger(logEntry)),
|
||||
sync.WithHealthOverride(lua.ResourceHealthOverrides(resourceOverrides)),
|
||||
sync.WithPermissionValidator(func(un *unstructured.Unstructured, res *v1.APIResource) error {
|
||||
@@ -242,6 +249,13 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
|
||||
m.isSelfReferencedObj(live, target, app.GetName(), appLabelKey, trackingMethod)
|
||||
}),
|
||||
sync.WithManifestValidation(!syncOp.SyncOptions.HasOption(common.SyncOptionsDisableValidation)),
|
||||
sync.WithNamespaceCreation(syncOp.SyncOptions.HasOption("CreateNamespace=true"), func(un *unstructured.Unstructured) bool {
|
||||
if un != nil && kube.GetAppInstanceLabel(un, cdcommon.LabelKeyAppInstance) != "" {
|
||||
kube.UnsetLabel(un, cdcommon.LabelKeyAppInstance)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}),
|
||||
sync.WithSyncWaveHook(delayBetweenSyncWaves),
|
||||
sync.WithPruneLast(syncOp.SyncOptions.HasOption(common.SyncOptionPruneLast)),
|
||||
sync.WithResourceModificationChecker(syncOp.SyncOptions.HasOption("ApplyOutOfSyncOnly=true"), compareResult.diffResultList),
|
||||
@@ -249,21 +263,6 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
|
||||
sync.WithReplace(syncOp.SyncOptions.HasOption(common.SyncOptionReplace)),
|
||||
sync.WithServerSideApply(syncOp.SyncOptions.HasOption(common.SyncOptionServerSideApply)),
|
||||
sync.WithServerSideApplyManager(cdcommon.ArgoCDSSAManager),
|
||||
}
|
||||
|
||||
if syncOp.SyncOptions.HasOption("CreateNamespace=true") {
|
||||
opts = append(opts, sync.WithNamespaceModifier(syncNamespace(m.resourceTracking, appLabelKey, trackingMethod, app.Name, app.Spec.SyncPolicy)))
|
||||
}
|
||||
|
||||
syncCtx, cleanup, err := sync.NewSyncContext(
|
||||
compareResult.syncStatus.Revision,
|
||||
reconciliationResult,
|
||||
restConfig,
|
||||
rawConfig,
|
||||
m.kubectl,
|
||||
app.Spec.Destination.Namespace,
|
||||
openAPISchema,
|
||||
opts...,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
gitopscommon "github.com/argoproj/gitops-engine/pkg/sync/common"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
// syncNamespace determine if Argo CD should create and/or manage the namespace
|
||||
// where the application will be deployed.
|
||||
func syncNamespace(resourceTracking argo.ResourceTracking, appLabelKey string, trackingMethod v1alpha1.TrackingMethod, appName string, syncPolicy *v1alpha1.SyncPolicy) func(m, l *unstructured.Unstructured) (bool, error) {
|
||||
// This function must return true for the managed namespace to be synced.
|
||||
return func(managedNs, liveNs *unstructured.Unstructured) (bool, error) {
|
||||
if managedNs == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
isNewNamespace := liveNs == nil
|
||||
isManagedNamespace := syncPolicy != nil && syncPolicy.ManagedNamespaceMetadata != nil
|
||||
|
||||
// should only sync the namespace if it doesn't exist in k8s or if
|
||||
// syncPolicy is defined to manage the metadata
|
||||
if !isManagedNamespace && !isNewNamespace {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if isManagedNamespace {
|
||||
managedNamespaceMetadata := syncPolicy.ManagedNamespaceMetadata
|
||||
managedNs.SetLabels(managedNamespaceMetadata.Labels)
|
||||
// managedNamespaceMetadata relies on SSA in order to avoid overriding
|
||||
// existing labels and annotations in namespaces
|
||||
managedNs.SetAnnotations(appendSSAAnnotation(managedNamespaceMetadata.Annotations))
|
||||
}
|
||||
|
||||
// TODO: https://github.com/argoproj/argo-cd/issues/11196
|
||||
// err := resourceTracking.SetAppInstance(managedNs, appLabelKey, appName, "", trackingMethod)
|
||||
// if err != nil {
|
||||
// return false, fmt.Errorf("failed to set app instance tracking on the namespace %s: %s", managedNs.GetName(), err)
|
||||
// }
|
||||
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// appendSSAAnnotation will set the managed namespace to be synced
|
||||
// with server-side apply
|
||||
func appendSSAAnnotation(in map[string]string) map[string]string {
|
||||
r := map[string]string{}
|
||||
for k, v := range in {
|
||||
r[k] = v
|
||||
}
|
||||
r[gitopscommon.AnnotationSyncOptions] = gitopscommon.SyncOptionServerSideApply
|
||||
return r
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func createFakeNamespace(uid string, resourceVersion string, labels map[string]string, annotations map[string]string) *unstructured.Unstructured {
|
||||
un := unstructured.Unstructured{}
|
||||
un.SetUID(types.UID(uid))
|
||||
un.SetResourceVersion(resourceVersion)
|
||||
un.SetLabels(labels)
|
||||
un.SetAnnotations(annotations)
|
||||
un.SetKind("Namespace")
|
||||
un.SetName("some-namespace")
|
||||
return &un
|
||||
}
|
||||
|
||||
func Test_shouldNamespaceSync(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
syncPolicy *v1alpha1.SyncPolicy
|
||||
managedNs *unstructured.Unstructured
|
||||
liveNs *unstructured.Unstructured
|
||||
expected bool
|
||||
expectedLabels map[string]string
|
||||
expectedAnnotations map[string]string
|
||||
}{
|
||||
{
|
||||
name: "liveNs is nil and syncPolicy is nil",
|
||||
expected: false,
|
||||
managedNs: nil,
|
||||
liveNs: nil,
|
||||
syncPolicy: nil,
|
||||
},
|
||||
{
|
||||
name: "liveNs is nil and syncPolicy is not nil",
|
||||
expected: false,
|
||||
managedNs: nil,
|
||||
liveNs: nil,
|
||||
syncPolicy: &v1alpha1.SyncPolicy{
|
||||
ManagedNamespaceMetadata: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "liveNs is nil and syncPolicy has labels and annotations",
|
||||
expected: false,
|
||||
managedNs: nil,
|
||||
liveNs: nil,
|
||||
expectedLabels: map[string]string{"my-cool-label": "some-value"},
|
||||
expectedAnnotations: map[string]string{"my-cool-annotation": "some-value"},
|
||||
syncPolicy: &v1alpha1.SyncPolicy{
|
||||
ManagedNamespaceMetadata: &v1alpha1.ManagedNamespaceMetadata{
|
||||
Labels: map[string]string{"my-cool-label": "some-value"},
|
||||
Annotations: map[string]string{"my-cool-annotation": "some-value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespace does not yet exist and managedNamespaceMetadata nil",
|
||||
expected: true,
|
||||
expectedLabels: map[string]string{},
|
||||
expectedAnnotations: map[string]string{},
|
||||
managedNs: createFakeNamespace("", "", map[string]string{}, map[string]string{}),
|
||||
liveNs: nil,
|
||||
syncPolicy: &v1alpha1.SyncPolicy{
|
||||
ManagedNamespaceMetadata: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespace does not yet exist and managedNamespaceMetadata not nil",
|
||||
expected: true,
|
||||
expectedAnnotations: map[string]string{"argocd.argoproj.io/sync-options": "ServerSideApply=true"},
|
||||
managedNs: createFakeNamespace("", "", map[string]string{}, map[string]string{}),
|
||||
liveNs: nil,
|
||||
syncPolicy: &v1alpha1.SyncPolicy{
|
||||
ManagedNamespaceMetadata: &v1alpha1.ManagedNamespaceMetadata{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespace does not yet exist and managedNamespaceMetadata has empty labels map",
|
||||
expected: true,
|
||||
expectedLabels: map[string]string{},
|
||||
expectedAnnotations: map[string]string{"argocd.argoproj.io/sync-options": "ServerSideApply=true"},
|
||||
managedNs: createFakeNamespace("", "", map[string]string{}, map[string]string{}),
|
||||
liveNs: nil,
|
||||
syncPolicy: &v1alpha1.SyncPolicy{
|
||||
ManagedNamespaceMetadata: &v1alpha1.ManagedNamespaceMetadata{
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespace does not yet exist and managedNamespaceMetadata has empty annotations map",
|
||||
expected: true,
|
||||
expectedAnnotations: map[string]string{"argocd.argoproj.io/sync-options": "ServerSideApply=true"},
|
||||
managedNs: createFakeNamespace("", "", map[string]string{}, map[string]string{}),
|
||||
liveNs: nil,
|
||||
syncPolicy: &v1alpha1.SyncPolicy{
|
||||
ManagedNamespaceMetadata: &v1alpha1.ManagedNamespaceMetadata{
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespace does not yet exist and managedNamespaceMetadata has empty annotations and labels map",
|
||||
expected: true,
|
||||
expectedLabels: map[string]string{},
|
||||
expectedAnnotations: map[string]string{"argocd.argoproj.io/sync-options": "ServerSideApply=true"},
|
||||
managedNs: createFakeNamespace("", "", map[string]string{}, map[string]string{}),
|
||||
liveNs: nil,
|
||||
syncPolicy: &v1alpha1.SyncPolicy{
|
||||
ManagedNamespaceMetadata: &v1alpha1.ManagedNamespaceMetadata{
|
||||
Labels: map[string]string{},
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespace does not yet exist and managedNamespaceMetadata has labels",
|
||||
expected: true,
|
||||
expectedLabels: map[string]string{"my-cool-label": "some-value"},
|
||||
expectedAnnotations: map[string]string{"argocd.argoproj.io/sync-options": "ServerSideApply=true"},
|
||||
managedNs: createFakeNamespace("", "", map[string]string{}, map[string]string{}),
|
||||
liveNs: nil,
|
||||
syncPolicy: &v1alpha1.SyncPolicy{
|
||||
ManagedNamespaceMetadata: &v1alpha1.ManagedNamespaceMetadata{
|
||||
Labels: map[string]string{"my-cool-label": "some-value"},
|
||||
Annotations: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespace does not yet exist and managedNamespaceMetadata has annotations",
|
||||
expected: true,
|
||||
expectedAnnotations: map[string]string{"my-cool-annotation": "some-value", "argocd.argoproj.io/sync-options": "ServerSideApply=true"},
|
||||
managedNs: createFakeNamespace("", "", map[string]string{}, map[string]string{}),
|
||||
liveNs: nil,
|
||||
syncPolicy: &v1alpha1.SyncPolicy{
|
||||
ManagedNamespaceMetadata: &v1alpha1.ManagedNamespaceMetadata{
|
||||
Labels: nil,
|
||||
Annotations: map[string]string{"my-cool-annotation": "some-value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespace does not yet exist and managedNamespaceMetadata has annotations and labels",
|
||||
expected: true,
|
||||
expectedLabels: map[string]string{"my-cool-label": "some-value"},
|
||||
expectedAnnotations: map[string]string{"my-cool-annotation": "some-value", "argocd.argoproj.io/sync-options": "ServerSideApply=true"},
|
||||
managedNs: createFakeNamespace("", "", map[string]string{}, map[string]string{}),
|
||||
liveNs: nil,
|
||||
syncPolicy: &v1alpha1.SyncPolicy{
|
||||
ManagedNamespaceMetadata: &v1alpha1.ManagedNamespaceMetadata{
|
||||
Labels: map[string]string{"my-cool-label": "some-value"},
|
||||
Annotations: map[string]string{"my-cool-annotation": "some-value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespace exists with no labels or annotations and managedNamespaceMetadata has labels",
|
||||
expected: true,
|
||||
expectedLabels: map[string]string{"my-cool-label": "some-value"},
|
||||
expectedAnnotations: map[string]string{"argocd.argoproj.io/sync-options": "ServerSideApply=true"},
|
||||
managedNs: createFakeNamespace("", "", map[string]string{}, map[string]string{}),
|
||||
liveNs: createFakeNamespace("something", "1", map[string]string{}, map[string]string{}),
|
||||
syncPolicy: &v1alpha1.SyncPolicy{
|
||||
ManagedNamespaceMetadata: &v1alpha1.ManagedNamespaceMetadata{
|
||||
Labels: map[string]string{"my-cool-label": "some-value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespace exists with no labels or annotations and managedNamespaceMetadata has annotations",
|
||||
expected: true,
|
||||
expectedAnnotations: map[string]string{"my-cool-annotation": "some-value", "argocd.argoproj.io/sync-options": "ServerSideApply=true"},
|
||||
managedNs: createFakeNamespace("", "", map[string]string{}, map[string]string{}),
|
||||
liveNs: createFakeNamespace("something", "1", map[string]string{}, map[string]string{}),
|
||||
syncPolicy: &v1alpha1.SyncPolicy{
|
||||
ManagedNamespaceMetadata: &v1alpha1.ManagedNamespaceMetadata{
|
||||
Annotations: map[string]string{"my-cool-annotation": "some-value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespace exists with no labels or annotations and managedNamespaceMetadata has annotations and labels",
|
||||
expected: true,
|
||||
expectedLabels: map[string]string{"my-cool-label": "some-value"},
|
||||
expectedAnnotations: map[string]string{"my-cool-annotation": "some-value", "argocd.argoproj.io/sync-options": "ServerSideApply=true"},
|
||||
managedNs: createFakeNamespace("", "", map[string]string{}, map[string]string{}),
|
||||
liveNs: createFakeNamespace("something", "1", map[string]string{}, map[string]string{}),
|
||||
syncPolicy: &v1alpha1.SyncPolicy{
|
||||
ManagedNamespaceMetadata: &v1alpha1.ManagedNamespaceMetadata{
|
||||
Labels: map[string]string{"my-cool-label": "some-value"},
|
||||
Annotations: map[string]string{"my-cool-annotation": "some-value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespace exists with labels and managedNamespaceMetadata has mismatching labels",
|
||||
expected: true,
|
||||
expectedAnnotations: map[string]string{"argocd.argoproj.io/sync-options": "ServerSideApply=true"},
|
||||
expectedLabels: map[string]string{"my-cool-label": "some-value", "my-other-label": "some-other-value"},
|
||||
managedNs: createFakeNamespace("", "", map[string]string{}, map[string]string{}),
|
||||
liveNs: createFakeNamespace("something", "1", map[string]string{"my-cool-label": "some-value"}, map[string]string{}),
|
||||
syncPolicy: &v1alpha1.SyncPolicy{
|
||||
ManagedNamespaceMetadata: &v1alpha1.ManagedNamespaceMetadata{
|
||||
Labels: map[string]string{"my-cool-label": "some-value", "my-other-label": "some-other-value"},
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespace exists with annotations and managedNamespaceMetadata has mismatching annotations",
|
||||
expected: true,
|
||||
expectedLabels: map[string]string{},
|
||||
expectedAnnotations: map[string]string{"my-cool-annotation": "some-value", "argocd.argoproj.io/sync-options": "ServerSideApply=true"},
|
||||
managedNs: createFakeNamespace("", "", map[string]string{}, map[string]string{}),
|
||||
liveNs: createFakeNamespace("something", "1", map[string]string{}, map[string]string{"my-cool-annotation": "some-value", "my-other-annotation": "some-other-value"}),
|
||||
syncPolicy: &v1alpha1.SyncPolicy{
|
||||
ManagedNamespaceMetadata: &v1alpha1.ManagedNamespaceMetadata{
|
||||
Labels: map[string]string{},
|
||||
Annotations: map[string]string{"my-cool-annotation": "some-value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespace exists with annotations and labels managedNamespaceMetadata has mismatching annotations and labels",
|
||||
expected: true,
|
||||
expectedLabels: map[string]string{"my-cool-label": "some-value", "my-other-label": "some-other-value"},
|
||||
expectedAnnotations: map[string]string{"my-cool-annotation": "some-value", "my-other-annotation": "some-other-value", "argocd.argoproj.io/sync-options": "ServerSideApply=true"},
|
||||
managedNs: createFakeNamespace("", "", map[string]string{}, map[string]string{}),
|
||||
liveNs: createFakeNamespace("something", "1", map[string]string{"my-cool-label": "some-value"}, map[string]string{"my-cool-annotation": "some-value"}),
|
||||
syncPolicy: &v1alpha1.SyncPolicy{
|
||||
ManagedNamespaceMetadata: &v1alpha1.ManagedNamespaceMetadata{
|
||||
Labels: map[string]string{"my-cool-label": "some-value", "my-other-label": "some-other-value"},
|
||||
Annotations: map[string]string{"my-cool-annotation": "some-value", "my-other-annotation": "some-other-value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual, err := syncNamespace(argo.NewResourceTracking(), common.LabelKeyAppInstance, argo.TrackingMethodAnnotation, "some-app", tt.syncPolicy)(tt.managedNs, tt.liveNs)
|
||||
assert.NoError(t, err)
|
||||
|
||||
if tt.managedNs != nil {
|
||||
assert.Equal(t, tt.expectedLabels, tt.managedNs.GetLabels())
|
||||
assert.Equal(t, tt.expectedAnnotations, tt.managedNs.GetAnnotations())
|
||||
}
|
||||
|
||||
assert.Equalf(t, tt.expected, actual, "syncNamespace(%v)", tt.syncPolicy)
|
||||
})
|
||||
}
|
||||
}
|
||||
BIN
docs/assets/extra_info-1.png
Normal file
BIN
docs/assets/extra_info-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/assets/extra_info-2.png
Normal file
BIN
docs/assets/extra_info-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.3 KiB |
BIN
docs/assets/extra_info.png
Normal file
BIN
docs/assets/extra_info.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
@@ -9,16 +9,6 @@ setTimeout(function() {
|
||||
caret.innerHTML = "<i class='fa fa-caret-down dropdown-caret'></i>"
|
||||
caret.classList.add('dropdown-caret')
|
||||
div.querySelector('.rst-current-version').appendChild(caret);
|
||||
div.querySelector('.rst-current-version').addEventListener('click', function() {
|
||||
const classes = container.className.split(' ');
|
||||
const index = classes.indexOf('shift-up');
|
||||
if (index === -1) {
|
||||
classes.push('shift-up');
|
||||
} else {
|
||||
classes.splice(index, 1);
|
||||
}
|
||||
container.className = classes.join(' ');
|
||||
});
|
||||
}
|
||||
|
||||
var CSSLink = document.createElement('link');
|
||||
|
||||
@@ -65,12 +65,12 @@ make builder-image IMAGE_NAMESPACE=argoproj IMAGE_TAG=v1.0.0
|
||||
|
||||
## Public CD
|
||||
|
||||
Every commit to master is built and published to `ghcr.io/argoproj/argo-cd/argocd:<version>-<short-sha>`. The list of images is available at
|
||||
Every commit to master is built and published to `ghcr.io/argoproj/argocd:<version>-<short-sha>`. The list of images is available at
|
||||
https://github.com/argoproj/argo-cd/packages.
|
||||
|
||||
!!! note
|
||||
GitHub docker registry [requires](https://github.community/t5/GitHub-Actions/docker-pull-from-public-GitHub-Package-Registry-fail-with-quot/m-p/32888#M1294) authentication to read
|
||||
even publicly available packages. Follow the steps from Kubernetes [documentation](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry)
|
||||
to configure image pull secret if you want to use `ghcr.io/argoproj/argo-cd/argocd` image.
|
||||
to configure image pull secret if you want to use `ghcr.io/argoproj/argocd` image.
|
||||
|
||||
The image is automatically deployed to the dev Argo CD instance: [https://cd.apps.argoproj.io/](https://cd.apps.argoproj.io/)
|
||||
|
||||
@@ -103,7 +103,7 @@ Design documents are usually submitted as PR and use [this template](https://git
|
||||
|
||||
Our community regularly meets virtually to discuss issues, ideas and enhancements around Argo CD. We do invite you to join this virtual meetings if you want to bring up certain things (including your enhancement proposals), participate in our triaging or just want to get to know other contributors.
|
||||
|
||||
The current cadence of our meetings is weekly, every Thursday at 4:15pm UTC (8:15am Pacific, 11:15am Eastern, 5:15pm Central European, 9:45pm Indian). We use Zoom to conduct these meetings.
|
||||
The current cadence of our meetings is weekly, every Thursday at 4pm UTC (9am Pacific, 12pm Eastern, 6pm Central European, 9:30pm Indian). We use Zoom to conduct these meetings.
|
||||
|
||||
* [Agenda document (Google Docs, includes Zoom link)](https://docs.google.com/document/d/1xkoFkVviB70YBzSEa4bDnu-rUZ1sIFtwKKG1Uw8XsY8)
|
||||
|
||||
|
||||
@@ -20,36 +20,6 @@ curl -sSfL https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/i
|
||||
## Connect
|
||||
Connect to one of the services, for example, to debug the main ArgoCD server run:
|
||||
```shell
|
||||
kubectl config set-context --current --namespace argocd
|
||||
telepresence helm install # Installs telepresence into your cluster
|
||||
telepresence connect # Starts the connection to your cluster (bound to the current namespace)
|
||||
telepresence intercept argocd-server --port 8080:http --env-file .envrc.remote # Starts the interception
|
||||
```
|
||||
* `--port` forwards traffic of remote port http to 8080 locally (use `--port 8080:https` if argocd-server terminates TLS)
|
||||
* `--env-file` writes all the environment variables of the remote pod into a local file, the variables are also set on the subprocess of the `--run` command
|
||||
|
||||
With this, any traffic that hits your argocd-server service in the cluster (e.g through a LB / ingress) will be forwarded to your laptop on port 8080. So that you can now start argocd-server locally to debug or test new code. If you launch argocd-server using the environment variables in `.envrc.remote`, he is able to fetch all the configmaps, secrets and so one from the cluster and transparently connect to the other microservices so that no further configuration should be neccesary and he behaves exactly the same as in the cluster.
|
||||
|
||||
List current status of Telepresence using:
|
||||
```shell
|
||||
telepresence status
|
||||
```
|
||||
|
||||
Stop the intercept using:
|
||||
```shell
|
||||
telepresence leave argocd-server-argocd
|
||||
```
|
||||
|
||||
And uninstall telepresence from your cluster:
|
||||
```shell
|
||||
telepresence helm uninstall
|
||||
```
|
||||
|
||||
See [this quickstart](https://www.telepresence.io/docs/latest/howtos/intercepts/) for more information on how to intercept services using Telepresence.
|
||||
|
||||
### Connect (telepresence v1)
|
||||
Use the following command instead:
|
||||
```shell
|
||||
telepresence --swap-deployment argocd-server --namespace argocd --env-file .envrc.remote --expose 8080:8080 --expose 8083:8083 --run bash
|
||||
```
|
||||
* `--swap-deployment` changes the argocd-server deployment
|
||||
@@ -57,6 +27,7 @@ telepresence --swap-deployment argocd-server --namespace argocd --env-file .envr
|
||||
* `--env-file` writes all the environment variables of the remote pod into a local file, the variables are also set on the subprocess of the `--run` command
|
||||
* `--run` defines which command to run once a connection is established, use `bash`, `zsh` or others
|
||||
|
||||
|
||||
## Debug
|
||||
Once a connection is established, use your favorite tools to start the server locally.
|
||||
|
||||
@@ -65,23 +36,21 @@ Once a connection is established, use your favorite tools to start the server lo
|
||||
* Run `./dist/argocd-server`
|
||||
|
||||
### VSCode
|
||||
In VSCode use the following launch configuration to run argocd-server:
|
||||
|
||||
In VSCode use the integrated terminal to run the Telepresence command to connect. Then, to run argocd-server service use the following configuration.
|
||||
Update the configuration file to point to kubeconfig file: `KUBECONFIG=` (required)
|
||||
```json
|
||||
{
|
||||
"name": "Launch argocd-server",
|
||||
"name": "Launch",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/cmd/main.go",
|
||||
"program": "${workspaceFolder}/cmd/argocd-server",
|
||||
"envFile": [
|
||||
"${workspaceFolder}/.envrc.remote",
|
||||
],
|
||||
"env": {
|
||||
"ARGOCD_BINARY_NAME": "argocd-server",
|
||||
"CGO_ENABLED": "0",
|
||||
"KUBECONFIG": "/path/to/kube/config"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
### Can I discuss my contribution ideas somewhere?
|
||||
|
||||
Sure thing! You can either open an Enhancement Proposal in our GitHub issue tracker or you can [join us on Slack](https://argoproj.github.io/community/join-slack) in channel #argo-contributors to discuss your ideas and get guidance for submitting a PR.
|
||||
Sure thing! You can either open an Enhancement Proposal in our GitHub issue tracker or you can [join us on Slack](https://argoproj.github.io/community/join-slack) in channel #argo-dev to discuss your ideas and get guidance for submitting a PR.
|
||||
|
||||
### No one has looked at my PR yet. Why?
|
||||
|
||||
|
||||
@@ -9,12 +9,7 @@ To test:
|
||||
```bash
|
||||
make serve-docs
|
||||
```
|
||||
|
||||
Check for broken external links:
|
||||
|
||||
```bash
|
||||
make lint-docs
|
||||
```
|
||||
Once running, you can view your locally built documentation at [http://0.0.0.0:8000/](http://0.0.0.0:8000/).
|
||||
|
||||
## Deploying
|
||||
|
||||
|
||||
49
docs/faq.md
49
docs/faq.md
@@ -45,9 +45,10 @@ a secret named `argocd-initial-admin-secret`.
|
||||
To change the password, edit the `argocd-secret` secret and update the `admin.password` field with a new bcrypt hash.
|
||||
|
||||
!!! note "Generating a bcrypt hash"
|
||||
Use the following command to generate a bcrypt hash for `admin.password`
|
||||
Use a trustworthy, offline `bcrypt` implementation such as the [Python bcrypt library](https://pypi.org/project/bcrypt/) to generate the hash.
|
||||
|
||||
argocd account bcrypt --password <YOUR-PASSWORD-HERE>
|
||||
pip3 install bcrypt
|
||||
python3 -c "import bcrypt; print(bcrypt.hashpw(b'YOUR-PASSWORD-HERE', bcrypt.gensalt()).decode())"
|
||||
|
||||
To apply the new password hash, use the following command (replacing the hash with your own):
|
||||
|
||||
@@ -122,9 +123,9 @@ To terminate the sync, click on the "synchronisation" then "terminate":
|
||||
|
||||
 
|
||||
|
||||
## Why Is My App Out Of Sync Even After Syncing?
|
||||
## Why Is My App `Out Of Sync` Even After Syncing?
|
||||
|
||||
Is some cases, the tool you use may conflict with Argo CD by adding the `app.kubernetes.io/instance` label. E.g. using
|
||||
In some cases, the tool you use may conflict with Argo CD by adding the `app.kubernetes.io/instance` label. E.g. using
|
||||
Kustomize common labels feature.
|
||||
|
||||
Argo CD automatically sets the `app.kubernetes.io/instance` label and uses it to determine which resources form the app.
|
||||
@@ -142,7 +143,7 @@ The default polling interval is 3 minutes (180 seconds).
|
||||
You can change the setting by updating the `timeout.reconciliation` value in the [argocd-cm](https://github.com/argoproj/argo-cd/blob/2d6ce088acd4fb29271ffb6f6023dbb27594d59b/docs/operator-manual/argocd-cm.yaml#L279-L282) config map. If there are any Git changes, ArgoCD will only update applications with the [auto-sync setting](user-guide/auto_sync.md) enabled. If you set it to `0` then Argo CD will stop polling Git repositories automatically and you can only use alternative methods such as [webhooks](operator-manual/webhook.md) and/or manual syncs for deploying applications.
|
||||
|
||||
|
||||
## Why Are My Resource Limits Out Of Sync?
|
||||
## Why Are My Resource Limits `Out Of Sync`?
|
||||
|
||||
Kubernetes has normalized your resource limits when they are applied, and then Argo CD has then compared the version in
|
||||
your generated manifests to the normalized one is Kubernetes - they won't match.
|
||||
@@ -157,7 +158,7 @@ E.g.
|
||||
To fix this use diffing
|
||||
customizations [settings](./user-guide/diffing.md#known-kubernetes-types-in-crds-resource-limits-volume-mounts-etc).
|
||||
|
||||
## How Do I Fix "invalid cookie, longer than max length 4093"?
|
||||
## How Do I Fix `invalid cookie, longer than max length 4093`?
|
||||
|
||||
Argo CD uses a JWT as the auth token. You likely are part of many groups and have gone over the 4KB limit which is set
|
||||
for cookies. You can get the list of groups by opening "developer tools -> network"
|
||||
@@ -224,4 +225,38 @@ resource.customizations.health.bitnami.com_SealedSecret: |
|
||||
hs.status = "Healthy"
|
||||
hs.message = "Controller doesn't report resource status"
|
||||
return hs
|
||||
```
|
||||
```
|
||||
|
||||
## How do I fix `The order in patch list … doesn't match $setElementOrder list: …`?
|
||||
|
||||
An application may trigger a sync error labeled a `ComparisonError` with a message like:
|
||||
|
||||
> The order in patch list: [map[name:**KEY_BC** value:150] map[name:**KEY_BC** value:500] map[name:**KEY_BD** value:250] map[name:**KEY_BD** value:500] map[name:KEY_BI value:something]] doesn't match $setElementOrder list: [map[name:KEY_AA] map[name:KEY_AB] map[name:KEY_AC] map[name:KEY_AD] map[name:KEY_AE] map[name:KEY_AF] map[name:KEY_AG] map[name:KEY_AH] map[name:KEY_AI] map[name:KEY_AJ] map[name:KEY_AK] map[name:KEY_AL] map[name:KEY_AM] map[name:KEY_AN] map[name:KEY_AO] map[name:KEY_AP] map[name:KEY_AQ] map[name:KEY_AR] map[name:KEY_AS] map[name:KEY_AT] map[name:KEY_AU] map[name:KEY_AV] map[name:KEY_AW] map[name:KEY_AX] map[name:KEY_AY] map[name:KEY_AZ] map[name:KEY_BA] map[name:KEY_BB] map[name:**KEY_BC**] map[name:**KEY_BD**] map[name:KEY_BE] map[name:KEY_BF] map[name:KEY_BG] map[name:KEY_BH] map[name:KEY_BI] map[name:**KEY_BC**] map[name:**KEY_BD**]]
|
||||
|
||||
|
||||
There are two parts to the message:
|
||||
|
||||
1. `The order in patch list: [`
|
||||
|
||||
This identifies values for items, especially items that appear multiple times:
|
||||
|
||||
> map[name:**KEY_BC** value:150] map[name:**KEY_BC** value:500] map[name:**KEY_BD** value:250] map[name:**KEY_BD** value:500] map[name:KEY_BI value:something]
|
||||
|
||||
You'll want to identify the keys that are duplicated -- you can focus on the first part, as each duplicated key will appear, once for each of its value with its value in the first list. The second list is really just
|
||||
|
||||
`]`
|
||||
|
||||
2. `doesn't match $setElementOrder list: [`
|
||||
|
||||
This includes all of the keys. It's included for debugging purposes -- you don't need to pay much attention to it. It will give you a hint about the precise location in the list for the duplicated keys:
|
||||
|
||||
> map[name:KEY_AA] map[name:KEY_AB] map[name:KEY_AC] map[name:KEY_AD] map[name:KEY_AE] map[name:KEY_AF] map[name:KEY_AG] map[name:KEY_AH] map[name:KEY_AI] map[name:KEY_AJ] map[name:KEY_AK] map[name:KEY_AL] map[name:KEY_AM] map[name:KEY_AN] map[name:KEY_AO] map[name:KEY_AP] map[name:KEY_AQ] map[name:KEY_AR] map[name:KEY_AS] map[name:KEY_AT] map[name:KEY_AU] map[name:KEY_AV] map[name:KEY_AW] map[name:KEY_AX] map[name:KEY_AY] map[name:KEY_AZ] map[name:KEY_BA] map[name:KEY_BB] map[name:**KEY_BC**] map[name:**KEY_BD**] map[name:KEY_BE] map[name:KEY_BF] map[name:KEY_BG] map[name:KEY_BH] map[name:KEY_BI] map[name:**KEY_BC**] map[name:**KEY_BD**]
|
||||
|
||||
`]`
|
||||
|
||||
In this case, the duplicated keys have been **emphasized** to help you identify the problematic keys. Many editors have the ability to highlight all instances of a string, using such an editor can help with such problems.
|
||||
|
||||
The most common instance of this error is with `env:` fields for `containers`.
|
||||
|
||||
!!! note "Dynamic applications"
|
||||
It's possible that your application is being generated by a tool in which case the duplication might not be evident within the scope of a single file. If you have trouble debugging this problem, consider filing a ticket to the owner of the generator tool asking them to improve its validation and error reporting.
|
||||
|
||||
@@ -78,10 +78,10 @@ The API server can then be accessed using https://localhost:8080
|
||||
The initial password for the `admin` account is auto-generated and stored as
|
||||
clear text in the field `password` in a secret named `argocd-initial-admin-secret`
|
||||
in your Argo CD installation namespace. You can simply retrieve this password
|
||||
using the `argocd` CLI:
|
||||
using `kubectl`:
|
||||
|
||||
```bash
|
||||
argocd admin initial-password
|
||||
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d; echo
|
||||
```
|
||||
|
||||
!!! warning
|
||||
|
||||
220
docs/operator-manual/app-any-namespace.md
Normal file
220
docs/operator-manual/app-any-namespace.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# Applications in any namespace
|
||||
|
||||
**Current feature state**: Beta
|
||||
|
||||
!!! warning
|
||||
Please read this documentation carefully before you enable this feature. Misconfiguration could lead to potential security issues.
|
||||
|
||||
## Introduction
|
||||
|
||||
As of version 2.5, Argo CD supports managing `Application` resources in namespaces other than the control plane's namespace (which is usually `argocd`), but this feature has to be explicitly enabled and configured appropriately.
|
||||
|
||||
Argo CD administrators can define a certain set of namespaces where `Application` resources may be created, updated and reconciled in. However, applications in these additional namespaces will only be allowed to use certain `AppProjects`, as configured by the Argo CD administrators. This allows ordinary Argo CD users (e.g. application teams) to use patterns like declarative management of `Application` resources, implementing app-of-apps and others without the risk of a privilege escalation through usage of other `AppProjects` that would exceed the permissions granted to the application teams.
|
||||
|
||||
Some manual steps will need to be performed by the Argo CD administrator in order to enable this feature.
|
||||
|
||||
!!! note
|
||||
This feature is considered beta as of now. Some of the implementation details may change over the course of time until it is promoted to a stable status. We will be happy if early adopters use this feature and provide us with bug reports and feedback.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Cluster-scoped Argo CD installation
|
||||
|
||||
This feature can only be enabled and used when your Argo CD is installed as a cluster-wide instance, so it has permissions to list and manipulate resources on a cluster scope. It will *not* work with an Argo CD installed in namespace-scoped mode.
|
||||
|
||||
### Switch resource tracking method
|
||||
|
||||
Also, while technically not necessary, it is strongly suggested that you switch the application tracking method from the default `label` setting to either `annotation` or `annotation+label`. The reasonsing for this is, that application names will be a composite of the namespace's name and the name of the `Application`, and this can easily exceed the 63 characters length limit imposed on label values. Annotations have a notably greater length limit.
|
||||
|
||||
To enable annotation based resource tracking, refer to the documentation about [resource tracking methods](../../user-guide/resource_tracking/)
|
||||
|
||||
## Implementation details
|
||||
|
||||
### Overview
|
||||
|
||||
In order for an application to be managed and reconciled outside the Argo CD's control plane namespace, two prerequisites must match:
|
||||
|
||||
1. The `Application`'s namespace must be explicitly enabled using the `--application-namespaces` parameter for the `argocd-application-controller` and `argocd-server` workloads. This parameter controls the list of namespaces that Argo CD will be allowed to source `Application` resources from globally. Any namespace not configured here cannot be used from any `AppProject`.
|
||||
1. The `AppProject` referenced by the `.spec.project` field of the `Application` must have the namespace listed in its `.spec.sourceNamespaces` field. This setting will determine whether an `Application` may use a certain `AppProject`. If an `Application` specifies an `AppProject` that is not allowed, Argo CD refuses to process this `Application`. As stated above, any namespace configured in the `.spec.sourceNamespaces` field must also be enabled globally.
|
||||
|
||||
`Applications` in different namespaces can be created and managed just like any other `Application` in the `argocd` namespace previously, either declaratively or through the Argo CD API (e.g. using the CLI, the web UI, the REST API, etc).
|
||||
|
||||
### Reconfigure Argo CD to allow certain namespaces
|
||||
|
||||
#### Change workload startup parameters
|
||||
|
||||
In order to enable this feature, the Argo CD administrator must reconfigure the `argocd-server` and `argocd-application-controller` workloads to add the `--application-namespaces` parameter to the container's startup command.
|
||||
|
||||
The `--application-namespaces` parameter takes a comma-separated list of namespaces where `Applications` are to be allowed in. Each entry of the list supports shell-style wildcards such as `*`, so for example the entry `app-team-*` would match `app-team-one` and `app-team-two`. To enable all namespaces on the cluster where Argo CD is running on, you can just specify `*`, i.e. `--application-namespaces=*`.
|
||||
|
||||
The startup parameters for both, the `argocd-server` and the `argocd-application-controller` can also be conveniently set up and kept in sync by specifying the `application.namespaces` settings in the `argocd-cmd-params-cm` ConfigMap _instead_ of changing the manifests for the respective workloads. For example:
|
||||
|
||||
```yaml
|
||||
data:
|
||||
application.namespaces: app-team-one, app-team-two
|
||||
```
|
||||
|
||||
would allow the `app-team-one` and `app-team-two` namespaces for managing `Application` resources. After a change to the `argocd-cmd-params-cm` namespace, the appropriate workloads need to be restarted:
|
||||
|
||||
```bash
|
||||
kubectl rollout restart -n argocd deployment argocd-server
|
||||
kubectl rollout restart -n argocd statefulset argocd-application-controller
|
||||
```
|
||||
|
||||
#### Adapt Kubernetes RBAC
|
||||
|
||||
We decided to not extend the Kubernetes RBAC for the `argocd-server` workload by default for the time being. If you want `Applications` in other namespaces to be managed by the Argo CD API (i.e. the CLI and UI), you need to extend the Kubernetes permissions for the `argocd-server` ServiceAccount.
|
||||
|
||||
We supply a `ClusterRole` and `ClusterRoleBinding` suitable for this purpose in the `examples/k8s-rbac/argocd-server-applications` directory. For a default Argo CD installation (i.e. installed to the `argocd` namespace), you can just apply them as-is:
|
||||
|
||||
```shell
|
||||
kubectl apply -f examples/k8s-rbac/argocd-server-applications/
|
||||
```
|
||||
|
||||
!!! note
|
||||
At some later point in time, we may make this cluster role part of the default installation manifests.
|
||||
|
||||
### Allowing additional namespaces in an AppProject
|
||||
|
||||
Any user with Kubernetes access to the Argo CD control plane's namespace (`argocd`), especially those with permissions to create or update `Applications` in a declarative way, is to be considered an Argo CD admin.
|
||||
|
||||
This prevented unprivileged Argo CD users from declaratively creating or managing `Applications` in the past. Those users were constrained to using the API instead, subject to Argo CD RBAC which ensures only `Applications` in allowed `AppProjects` were created.
|
||||
|
||||
For an `Application` to be created outside the `argocd` namespace, the `AppProject` referred to in the `Application`'s `.spec.project` field must include the `Application`'s namespace in its `.spec.sourceNamespaces` field.
|
||||
|
||||
For example, consider the two following (incomplete) `AppProject` specs:
|
||||
|
||||
```yaml
|
||||
kind: AppProject
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
metadata:
|
||||
name: project-one
|
||||
namespace: argocd
|
||||
spec:
|
||||
sourceNamespaces:
|
||||
- namespace-one
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```yaml
|
||||
kind: AppProject
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
metadata:
|
||||
name: project-two
|
||||
namespace: argocd
|
||||
spec:
|
||||
sourceNamespaces:
|
||||
- namespace-two
|
||||
```
|
||||
|
||||
In order for an Application to set `.spec.project` to `project-one`, it would have to be created in either namespace `namespace-one` or `argocd`. Likewise, in order for an Application to set `.spec.project` to `project-two`, it would have to be created in either namespace `namespace-two` or `argocd`.
|
||||
|
||||
If an Application in `namespace-two` would set their `.spec.project` to `project-one` or an Application in `namespace-one` would set their `.spec.project` to `project-two`, Argo CD would consider this as a permission violation and refuse to reconcile the Application.
|
||||
|
||||
Also, the Argo CD API will enforce these constraints, regardless of the Argo CD RBAC permissions.
|
||||
|
||||
The `.spec.sourceNamespaces` field of the `AppProject` is a list that can contain an arbitrary amount of namespaces, and each entry supports shell-style wildcard, so that you can allow namespaces with patterns like `team-one-*`.
|
||||
|
||||
!!! warning
|
||||
Do not add user controlled namespaces in the `.spec.sourceNamespaces` field of any privileged AppProject like the `default` project. Always make sure that the AppProject follows the principle of granting least required privileges. Never grant access to the `argocd` namespace within the AppProject.
|
||||
|
||||
!!! note
|
||||
For backwards compatibility, Applications in the Argo CD control plane's namespace (`argocd`) are allowed to set their `.spec.project` field to reference any AppProject, regardless of the restrictions placed by the AppProject's `.spec.sourceNamespaces` field.
|
||||
|
||||
### Application names
|
||||
|
||||
For the CLI and UI, applications are now referred to and displayed as in the format `<namespace>/<name>`.
|
||||
|
||||
For backwards compatibility, if the namespace of the Application is the control plane's namespace (i.e. `argocd`), the `<namespace>` can be omitted from the application name when referring to it. For example, the application names `argocd/someapp` and `someapp` are semantically the same and refer to the same application in the CLI and the UI.
|
||||
|
||||
### Application RBAC
|
||||
|
||||
The RBAC syntax for Application objects has been changed from `<project>/<application>` to `<project>/<namespace>/<application>` to accomodate the need to restrict access based on the source namespace of the Application to be managed.
|
||||
|
||||
For backwards compatibility, Applications in the `argocd` namespace can still be refered to as `<project>/<application>` in the RBAC policy rules.
|
||||
|
||||
Wildcards do not make any distinction between project and application namespaces yet. For example, the following RBAC rule would match any application belonging to project `foo`, regardless of the namespace it is created in:
|
||||
|
||||
```
|
||||
p, somerole, applications, get, foo/*, allow
|
||||
```
|
||||
|
||||
If you want to restrict access to be granted only to `Applications` in project `foo` within namespace `bar`, the rule would need to be adapted as follows:
|
||||
|
||||
```
|
||||
p, somerole, applications, get, foo/bar/*, allow
|
||||
```
|
||||
|
||||
## Managing applications in other namespaces
|
||||
|
||||
### Declaratively
|
||||
|
||||
For declarative management of Applications, just create the Application from a YAML or JSON manifest in the desired namespace. Make sure that the `.spec.project` field refers to an AppProject that allows this namespace. For example, the following (incomplete) Application manifest creates an Application in the namespace `some-namespace`:
|
||||
|
||||
```yaml
|
||||
kind: Application
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
metadata:
|
||||
name: some-app
|
||||
namespace: some-namespace
|
||||
spec:
|
||||
project: some-project
|
||||
# ...
|
||||
```
|
||||
|
||||
The project `some-project` will then need to specify `some-namespace` in the list of allowed source namespaces, e.g.
|
||||
|
||||
```yaml
|
||||
kind: AppProject
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
metadata:
|
||||
name: some-project
|
||||
namespace: argocd
|
||||
spec:
|
||||
sourceNamespaces:
|
||||
- some-namespace
|
||||
```
|
||||
|
||||
### Using the CLI
|
||||
|
||||
You can use all existing Argo CD CLI commands for managing applications in other namespaces, exactly as you would use the CLI to manage applications in the control plane's namespace.
|
||||
|
||||
For example, to retrieve the `Application` named `foo` in the namespace `bar`, you can use the following CLI command:
|
||||
|
||||
```shell
|
||||
argocd app get foo/bar
|
||||
```
|
||||
|
||||
Likewise, to manage this application, keep referring to it as `foo/bar`:
|
||||
|
||||
```bash
|
||||
# Create an application
|
||||
argocd app create foo/bar ...
|
||||
# Sync the application
|
||||
argocd app sync foo/bar
|
||||
# Delete the application
|
||||
argocd app delete foo/bar
|
||||
# Retrieve application's manifest
|
||||
argocd app manifests foo/bar
|
||||
```
|
||||
|
||||
As stated previously, for applications in the Argo CD's control plane namespace, you can omit the namespace from the application name.
|
||||
|
||||
### Using the UI
|
||||
|
||||
Similar to the CLI, you can refer to the application in the UI as `foo/bar`.
|
||||
|
||||
For example, to create an application named `bar` in the namespace `foo` in the web UI, set the application name in the creation dialogue's _Application Name_ field to `foo/bar`. If the namespace is omitted, the control plane's namespace will be used.
|
||||
|
||||
### Using the REST API
|
||||
|
||||
If you are using the REST API, the namespace for `Application` cannot be specified as the application name, and resources need to be specified using the optional `appNamespace` query parameter. For example, to work with the `Application` resource named `foo` in the namespace `bar`, the request would look like follows:
|
||||
|
||||
```bash
|
||||
GET /api/v1/applications/foo?appNamespace=bar
|
||||
```
|
||||
|
||||
For other operations such as `POST` and `PUT`, the `appNamespace` parameter must be part of the request's payload.
|
||||
|
||||
For `Application` resources in the control plane namespace, this parameter can be omitted.
|
||||
@@ -6,7 +6,10 @@ metadata:
|
||||
namespace: argocd
|
||||
# Add this finalizer ONLY if you want these to cascade delete.
|
||||
finalizers:
|
||||
# The default behaviour is foreground cascading deletion
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
# Alternatively, you can use background cascading deletion
|
||||
# - resources-finalizer.argocd.argoproj.io/background
|
||||
# Add labels to your application object.
|
||||
labels:
|
||||
name: guestbook
|
||||
@@ -45,6 +48,9 @@ spec:
|
||||
valueFiles:
|
||||
- values-prod.yaml
|
||||
|
||||
# Ignore locally missing valueFiles when installing Helm chart. Defaults to false
|
||||
ignoreMissingValueFiles: false
|
||||
|
||||
# Values file as block file
|
||||
values: |
|
||||
ingress:
|
||||
@@ -61,6 +67,9 @@ spec:
|
||||
hosts:
|
||||
- mydomain.example.com
|
||||
|
||||
# Skip custom resource definition installation if chart contains custom resource definitions. Defaults to false
|
||||
skipCrds: false
|
||||
|
||||
# Optional Helm version to template with. If omitted it will fall back to look at the 'apiVersion' in Chart.yaml
|
||||
# and decide which Helm binary to use automatically. This field can be either 'v2' or 'v3'.
|
||||
version: v2
|
||||
@@ -108,7 +117,6 @@ spec:
|
||||
|
||||
# plugin specific config
|
||||
plugin:
|
||||
# NOTE: this field is deprecated in v2.5 and must be removed to use sidecar-based plugins.
|
||||
# Only set the plugin name if the plugin is defined in argocd-cm.
|
||||
# If the plugin is defined as a sidecar, omit the name. The plugin will be automatically matched with the
|
||||
# Application according to the plugin's discovery rules.
|
||||
@@ -117,22 +125,21 @@ spec:
|
||||
env:
|
||||
- name: FOO
|
||||
value: bar
|
||||
# Plugin parameters are new in v2.5.
|
||||
parameters:
|
||||
- name: string-param
|
||||
string: example-string
|
||||
- name: array-param
|
||||
array: [item1, item2]
|
||||
- name: map-param
|
||||
map:
|
||||
param-name: param-value
|
||||
|
||||
# Destination cluster and namespace to deploy the application
|
||||
destination:
|
||||
# cluster API URL
|
||||
server: https://kubernetes.default.svc
|
||||
# or cluster name
|
||||
# name: in-cluster
|
||||
# The namespace will only be set for namespace-scoped resources that have not set a value for .metadata.namespace
|
||||
namespace: guestbook
|
||||
|
||||
|
||||
# Extra information to show in the Argo CD Application details tab
|
||||
info:
|
||||
- name: 'Example:'
|
||||
value: 'https://example.com'
|
||||
|
||||
# Sync policy
|
||||
syncPolicy:
|
||||
automated: # automated sync by default retries failed attempts 5 times with following delays between attempts ( 5s, 10s, 20s, 40s, 80s ); retry controlled using `retry` field.
|
||||
@@ -144,15 +151,6 @@ spec:
|
||||
- CreateNamespace=true # Namespace Auto-Creation ensures that namespace specified as the application destination exists in the destination cluster.
|
||||
- PrunePropagationPolicy=foreground # Supported policies are background, foreground and orphan.
|
||||
- PruneLast=true # Allow the ability for resource pruning to happen as a final, implicit wave of a sync operation
|
||||
managedNamespaceMetadata: # Sets the metadata for the application namespace. Only valid if CreateNamespace=true (see above), otherwise it's a no-op.
|
||||
labels: # The labels to set on the application namespace
|
||||
any: label
|
||||
you: like
|
||||
annotations: # The annotations to set on the application namespace
|
||||
the: same
|
||||
applies: for
|
||||
annotations: on-the-namespace
|
||||
|
||||
# The retry feature is available since v1.7
|
||||
retry:
|
||||
limit: 5 # number of failed sync attempt retries; unlimited number of attempts if less than 0
|
||||
@@ -174,6 +172,9 @@ spec:
|
||||
kind: "*"
|
||||
managedFieldsManagers:
|
||||
- kube-controller-manager
|
||||
# Name and namespace are optional. If specified, they must match exactly, these are not glob patterns.
|
||||
name: my-deployment
|
||||
namespace: my-namespace
|
||||
|
||||
# RevisionHistoryLimit limits the number of items kept in the application's revision history, which is used for
|
||||
# informational purposes as well as for rollbacks to previous versions. This should only be changed in exceptional
|
||||
|
||||
@@ -108,7 +108,7 @@ spec:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: '{{path.basename}}'
|
||||
```
|
||||
(*The full example can be found [here](https://github.com/argoproj/argo-cd/tree/master/examples/applicationset/git-generator-directory/excludes).*)
|
||||
(*The full example can be found [here](https://github.com/argoproj/argo-cd/tree/master/applicationset/examples/git-generator-directory/excludes).*)
|
||||
|
||||
This example excludes the `exclude-helm-guestbook` directory from the list of directories scanned for this `ApplicationSet` resource.
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Post Selector all generators
|
||||
|
||||
The Selector allows to post-filter based on generated values using the kubernetes common labelSelector format. In the example, the list generator generates a set of two application which then filter by the key value to only select the `env` with value `staging`:
|
||||
|
||||
## Example: List generator + Post Selector
|
||||
```yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ApplicationSet
|
||||
metadata:
|
||||
name: guestbook
|
||||
spec:
|
||||
generators:
|
||||
- list:
|
||||
elements:
|
||||
- cluster: engineering-dev
|
||||
url: https://kubernetes.default.svc
|
||||
env: staging
|
||||
- cluster: engineering-prod
|
||||
url: https://kubernetes.default.svc
|
||||
env: prod
|
||||
selector:
|
||||
matchLabels:
|
||||
env: staging
|
||||
template:
|
||||
metadata:
|
||||
name: '{{cluster}}-guestbook'
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/argoproj-labs/applicationset.git
|
||||
targetRevision: HEAD
|
||||
path: examples/list-generator/guestbook/{{cluster}}
|
||||
destination:
|
||||
server: '{{url}}'
|
||||
namespace: guestbook
|
||||
```
|
||||
|
||||
The List generator + Post Selector generates a single set of parameters:
|
||||
```yaml
|
||||
- cluster: engineering-dev
|
||||
url: https://kubernetes.default.svc
|
||||
env: staging
|
||||
```
|
||||
@@ -60,7 +60,7 @@ spec:
|
||||
* `repo`: Required name of the GitHub repository.
|
||||
* `api`: If using GitHub Enterprise, the URL to access it. (Optional)
|
||||
* `tokenRef`: A `Secret` name and key containing the GitHub access token to use for requests. If not specified, will make anonymous requests which have a lower rate limit and can only see public repositories. (Optional)
|
||||
* `labels`: Labels is used to filter the PRs that you want to target. (Optional)
|
||||
* `labels`: Filter the PRs to those containing **all** of the labels listed. (Optional)
|
||||
* `appSecretName`: A `Secret` name containing a GitHub App secret in [repo-creds format][repo-creds].
|
||||
|
||||
[repo-creds]: ../declarative-setup.md#repository-credentials
|
||||
@@ -318,3 +318,7 @@ The Pull Request Generator will requeue when the next action occurs.
|
||||
- `merge`
|
||||
|
||||
For more information about each event, please refer to the [official documentation](https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html#merge-request-events).
|
||||
|
||||
## Lifecycle
|
||||
|
||||
An Application will be generated when a Pull Request is discovered when the configured criteria is met - i.e. for GitHub when a Pull Request matches the specified `labels` and/or `pullRequestState`. Application will be removed when a Pull Request no longer meets the specified criteria.
|
||||
|
||||
@@ -219,6 +219,41 @@ spec:
|
||||
* `api`: Optional. URL to Azure DevOps. If not set, `https://dev.azure.com` is used.
|
||||
* `allBranches`: Optional, default `false`. If `true`, scans every branch of eligible repositories. If `false`, check only the default branch of the eligible repositories.
|
||||
|
||||
## Bitbucket Cloud
|
||||
|
||||
The Bitbucket mode uses the Bitbucket API V2 to scan a workspace in bitbucket.org.
|
||||
|
||||
```yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ApplicationSet
|
||||
metadata:
|
||||
name: myapps
|
||||
spec:
|
||||
generators:
|
||||
- scmProvider:
|
||||
bitbucket:
|
||||
# The workspace id (slug).
|
||||
owner: "example-owner"
|
||||
# The user to use for basic authentication with an app password.
|
||||
user: "example-user"
|
||||
# If true, scan every branch of every repository. If false, scan only the main branch. Defaults to false.
|
||||
allBranches: true
|
||||
# Reference to a Secret containing an app password.
|
||||
appPasswordRef:
|
||||
secretName: appPassword
|
||||
key: password
|
||||
template:
|
||||
# ...
|
||||
```
|
||||
|
||||
* `owner`: The workspace ID (slug) to use when looking up repositories.
|
||||
* `user`: The user to use for authentication to the Bitbucket API V2 at bitbucket.org.
|
||||
* `allBranches`: By default (false) the template will only be evaluated for the main branch of each repo. If this is true, every branch of every repository will be passed to the filters. If using this flag, you likely want to use a `branchMatch` filter.
|
||||
* `appPasswordRef`: A `Secret` name and key containing the bitbucket app password to use for requests.
|
||||
|
||||
This SCM provider does not yet support label filtering
|
||||
|
||||
Available clone protocols are `ssh` and `https`.
|
||||
|
||||
## Filters
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user