Compare commits
321 Commits
release-2.
...
v2.11.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f344d54a4 | ||
|
|
e01bb5303a | ||
|
|
320abb8d64 | ||
|
|
46342a9e82 | ||
|
|
cf17283ebe | ||
|
|
25f7504ecc | ||
|
|
2b463d4103 | ||
|
|
9d58e7e330 | ||
|
|
212a6ed05a | ||
|
|
140ffdda4d | ||
|
|
47e7470726 | ||
|
|
9f40df0c29 | ||
|
|
6ef7b62a0f | ||
|
|
f1a449e83e | ||
|
|
6530c6fede | ||
|
|
786e141047 | ||
|
|
37dd289240 | ||
|
|
eee5c06eff | ||
|
|
4621b3b528 | ||
|
|
faeede3dc3 | ||
|
|
dd4ee83442 | ||
|
|
d3f33c0019 | ||
|
|
8cd8305212 | ||
|
|
da6c2e9c08 | ||
|
|
66f4934ecb | ||
|
|
20fd621aa2 | ||
|
|
f875931992 | ||
|
|
e1f890d176 | ||
|
|
602f5445b1 | ||
|
|
617f8a414f | ||
|
|
0460b9873e | ||
|
|
25c6653d8a | ||
|
|
9f186dab30 | ||
|
|
fb573e0008 | ||
|
|
35a2ebe428 | ||
|
|
3bb7ac92e8 | ||
|
|
2ef8fe2246 | ||
|
|
ce0e3bc7f0 | ||
|
|
29cdd31572 | ||
|
|
24ef7775e7 | ||
|
|
b71f0c8b54 | ||
|
|
edcf167be8 | ||
|
|
be48990126 | ||
|
|
9d5b17403f | ||
|
|
f491935eb9 | ||
|
|
040eb36740 | ||
|
|
6523f251b8 | ||
|
|
4e46a5e8fa | ||
|
|
618a4e914b | ||
|
|
3654d7f941 | ||
|
|
c09e5b0003 | ||
|
|
a4b8c6645b | ||
|
|
44da2063c7 | ||
|
|
4b11524242 | ||
|
|
c8d912f104 | ||
|
|
614f44c26c | ||
|
|
affd1cb251 | ||
|
|
405949b127 | ||
|
|
f287daba0d | ||
|
|
7deafc4014 | ||
|
|
766a6da2cd | ||
|
|
b711c5b7d7 | ||
|
|
e26f4fbdc1 | ||
|
|
8631e7ef9b | ||
|
|
ae29279cbe | ||
|
|
53b08426bc | ||
|
|
4b80393108 | ||
|
|
31aa4d9af9 | ||
|
|
e9547bce42 | ||
|
|
442dac12a7 | ||
|
|
ad372cf716 | ||
|
|
1bddee2e5d | ||
|
|
12ccb52498 | ||
|
|
295dff6a38 | ||
|
|
a5a499a2c6 | ||
|
|
f87897c53c | ||
|
|
8cf03812a1 | ||
|
|
38d86a911e | ||
|
|
c4fdc54195 | ||
|
|
37c5f4d8ee | ||
|
|
a49880e0a5 | ||
|
|
2b28683419 | ||
|
|
ec7b49d82d | ||
|
|
f0490090cd | ||
|
|
427965c497 | ||
|
|
7350a55e57 | ||
|
|
3ec63b222c | ||
|
|
d11e146ab5 | ||
|
|
1c938e2aea | ||
|
|
5ada5c2810 | ||
|
|
ed0218f98e | ||
|
|
997688e94b | ||
|
|
09407a21be | ||
|
|
16fc00841e | ||
|
|
d69c61ae1a | ||
|
|
6596e088ac | ||
|
|
63a72ee1e7 | ||
|
|
2f58d73612 | ||
|
|
2b75efd24a | ||
|
|
aaabb050b2 | ||
|
|
4a92ab782f | ||
|
|
739fa0c26e | ||
|
|
138b37bd6c | ||
|
|
77899cb285 | ||
|
|
f0b03071fc | ||
|
|
3b8f673f06 | ||
|
|
479b5544b5 | ||
|
|
51cfd50dd9 | ||
|
|
85a2145401 | ||
|
|
27c174384b | ||
|
|
be69bcc010 | ||
|
|
f4bb860fb8 | ||
|
|
565aa8e1f5 | ||
|
|
e4885db6ad | ||
|
|
0b5d9afd40 | ||
|
|
2a747c65ed | ||
|
|
5b77e8d448 | ||
|
|
86369ca71d | ||
|
|
7cfb9d6e13 | ||
|
|
ff055300a2 | ||
|
|
de44e14d90 | ||
|
|
e60996814e | ||
|
|
57d6e6557b | ||
|
|
dd29300fc1 | ||
|
|
dc242da748 | ||
|
|
76c64796cc | ||
|
|
6ca29a3c0b | ||
|
|
cc235e4a06 | ||
|
|
05eea87162 | ||
|
|
085ed0f65a | ||
|
|
9b965700b3 | ||
|
|
31776d49f4 | ||
|
|
de4cac4165 | ||
|
|
0fe1acb357 | ||
|
|
da49d3eed9 | ||
|
|
542890f739 | ||
|
|
fd3462e9c0 | ||
|
|
1901cb56bc | ||
|
|
3fee8cbf81 | ||
|
|
98a888ed52 | ||
|
|
7bb92d7d61 | ||
|
|
2b6b9bf93e | ||
|
|
2ad06a6308 | ||
|
|
edc6f5f39e | ||
|
|
138a112172 | ||
|
|
fda25d0b93 | ||
|
|
d76976ff12 | ||
|
|
8aa9625870 | ||
|
|
0c2934a339 | ||
|
|
0d020f0079 | ||
|
|
d0331eefe4 | ||
|
|
1b919879ab | ||
|
|
58993b1a01 | ||
|
|
4db89427a8 | ||
|
|
8786ec243a | ||
|
|
ee78d02a42 | ||
|
|
24ac326384 | ||
|
|
839526e976 | ||
|
|
7eda6e01f4 | ||
|
|
88e4da625e | ||
|
|
e2f87940e4 | ||
|
|
82e20a4fc9 | ||
|
|
3cf54af2be | ||
|
|
e3ee9ee831 | ||
|
|
28112c65a4 | ||
|
|
99128c27f5 | ||
|
|
e9b1af5885 | ||
|
|
f132628866 | ||
|
|
d73304ea1c | ||
|
|
62003f0152 | ||
|
|
d4251ef7cf | ||
|
|
a4b5051538 | ||
|
|
bb4e47a12d | ||
|
|
c973f7013b | ||
|
|
e492e1469c | ||
|
|
48f4392b26 | ||
|
|
8b89722eee | ||
|
|
5ffbca4cce | ||
|
|
d42004fa86 | ||
|
|
891d089304 | ||
|
|
2ac96a55ba | ||
|
|
09798b5713 | ||
|
|
4d1eb5515f | ||
|
|
37eacec208 | ||
|
|
8712d03e9e | ||
|
|
99723143b9 | ||
|
|
6de1037eb3 | ||
|
|
e4c8568393 | ||
|
|
c435260f13 | ||
|
|
8bb41eda04 | ||
|
|
7fe1263300 | ||
|
|
5bc1850aa1 | ||
|
|
4dc91dcb9d | ||
|
|
262d287645 | ||
|
|
df2b0e2711 | ||
|
|
5d4c0ecdee | ||
|
|
6aa79f283c | ||
|
|
17ef8b9579 | ||
|
|
4761255608 | ||
|
|
d55e926a63 | ||
|
|
3cc02779ca | ||
|
|
fa1ad0c375 | ||
|
|
b80015e27e | ||
|
|
8a866492db | ||
|
|
078eb6c56d | ||
|
|
d9e0666795 | ||
|
|
47eddf169e | ||
|
|
4e224ee878 | ||
|
|
50284f7c5c | ||
|
|
c0e679a66c | ||
|
|
d5a4f81b8e | ||
|
|
d5b0a4f029 | ||
|
|
79e94b8fe0 | ||
|
|
ff7192bfc5 | ||
|
|
6d0ba1fad7 | ||
|
|
5d6111b745 | ||
|
|
db34f98451 | ||
|
|
5406a1a5e8 | ||
|
|
c082a0cca5 | ||
|
|
82433ff1a8 | ||
|
|
4458d5fa80 | ||
|
|
adceae9ec8 | ||
|
|
bb1c1ed44d | ||
|
|
d7da05f3aa | ||
|
|
7e80f1e8e5 | ||
|
|
f77cf94908 | ||
|
|
52ffd7df4d | ||
|
|
3c9a2fbc59 | ||
|
|
93a668ac09 | ||
|
|
98d5a2bf86 | ||
|
|
7ce342fb88 | ||
|
|
b23e71f578 | ||
|
|
8ac7b6da38 | ||
|
|
d494d3a331 | ||
|
|
b93874e741 | ||
|
|
af20dae498 | ||
|
|
4bf4629231 | ||
|
|
c4a9df6570 | ||
|
|
ca27c41bc2 | ||
|
|
0b22a1198a | ||
|
|
228eda5e1e | ||
|
|
555f6f42d2 | ||
|
|
5100726fd6 | ||
|
|
769836e6ea | ||
|
|
2082a21121 | ||
|
|
b1c6dc5742 | ||
|
|
5246429cad | ||
|
|
3fda27e8d9 | ||
|
|
55713b3474 | ||
|
|
dc1ccea568 | ||
|
|
fa31c2323a | ||
|
|
b8aeb781a6 | ||
|
|
0c8bc1d61e | ||
|
|
28f362b886 | ||
|
|
f4019b7657 | ||
|
|
291445f132 | ||
|
|
c691d366a5 | ||
|
|
7f749c62b8 | ||
|
|
55918abd77 | ||
|
|
4d53d36268 | ||
|
|
981bceecb0 | ||
|
|
e5c88c914b | ||
|
|
3c21242356 | ||
|
|
be1f0eafb8 | ||
|
|
4e084ace8c | ||
|
|
8c9abb27ef | ||
|
|
344f23b5e8 | ||
|
|
85009d941c | ||
|
|
8932036d53 | ||
|
|
6d0850749b | ||
|
|
666499f610 | ||
|
|
0012e787f3 | ||
|
|
fec5708ea5 | ||
|
|
97727cbb59 | ||
|
|
b234264d79 | ||
|
|
c29f6da00c | ||
|
|
f7236d794b | ||
|
|
9042f415b7 | ||
|
|
397063fea4 | ||
|
|
7302a52ea1 | ||
|
|
9ecc5aec2a | ||
|
|
21c384f423 | ||
|
|
32e373829b | ||
|
|
80683acb71 | ||
|
|
8a0bf41863 | ||
|
|
f0cbf516fc | ||
|
|
65869a3860 | ||
|
|
2b95bc0d3e | ||
|
|
d5e119c251 | ||
|
|
d367b727c8 | ||
|
|
3997dbef8e | ||
|
|
c7bf0648e5 | ||
|
|
256c2ae5dc | ||
|
|
180b99010e | ||
|
|
15060e1d73 | ||
|
|
7ec9999b01 | ||
|
|
d9df2525c5 | ||
|
|
b12630c4be | ||
|
|
54de532940 | ||
|
|
cd4fc97c9d | ||
|
|
54f1572d46 | ||
|
|
d6da9f2a15 | ||
|
|
9b27aeb1a4 | ||
|
|
2024659696 | ||
|
|
8ebe1cd3c4 | ||
|
|
c5b9c67073 | ||
|
|
40760eb852 | ||
|
|
ecbd24da10 | ||
|
|
c4ac5aaa97 | ||
|
|
4afddf71cc | ||
|
|
1372529d56 | ||
|
|
d5955508da | ||
|
|
1975074de5 | ||
|
|
a40330f5c8 | ||
|
|
80ca563909 | ||
|
|
f0f4a4e438 | ||
|
|
20f7182489 | ||
|
|
7d1f6a1e94 | ||
|
|
0e67ed89ac | ||
|
|
7847e7f393 | ||
|
|
3224102664 |
140
.github/workflows/ci-build.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
name: Integration tests
|
||||
on:
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
@@ -23,12 +23,35 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
backend: ${{ steps.filter.outputs.backend_any_changed }}
|
||||
frontend: ${{ steps.filter.outputs.frontend_any_changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
- uses: tj-actions/changed-files@90a06d6ba9543371ab4df8eeca0be07ca6054959 # v42.0.2
|
||||
id: filter
|
||||
with:
|
||||
# Any file which is not under docs/, ui/ or is not a markdown file is counted as a backend file
|
||||
files_yaml: |
|
||||
backend:
|
||||
- '!ui/**'
|
||||
- '!**.md'
|
||||
- '!**/*.md'
|
||||
- '!docs/**'
|
||||
frontend:
|
||||
- 'ui/**'
|
||||
- Dockerfile
|
||||
check-go:
|
||||
name: Ensure Go modules synchronicity
|
||||
if: ${{ needs.changes.outputs.backend == 'true' }}
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- changes
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
|
||||
with:
|
||||
@@ -36,17 +59,20 @@ jobs:
|
||||
- name: Download all Go modules
|
||||
run: |
|
||||
go mod download
|
||||
- name: Check for tidyness of go.mod and go.sum
|
||||
- name: Check for tidiness of go.mod and go.sum
|
||||
run: |
|
||||
go mod tidy
|
||||
git diff --exit-code -- .
|
||||
|
||||
build-go:
|
||||
name: Build & cache Go code
|
||||
if: ${{ needs.changes.outputs.backend == 'true' }}
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- changes
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
|
||||
with:
|
||||
@@ -67,10 +93,13 @@ jobs:
|
||||
contents: read # for actions/checkout to fetch code
|
||||
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
|
||||
name: Lint Go code
|
||||
if: ${{ needs.changes.outputs.backend == 'true' }}
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- changes
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
|
||||
with:
|
||||
@@ -83,17 +112,19 @@ jobs:
|
||||
|
||||
test-go:
|
||||
name: Run unit tests for Go packages
|
||||
if: ${{ needs.changes.outputs.backend == 'true' }}
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- build-go
|
||||
- changes
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
|
||||
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
|
||||
steps:
|
||||
- name: Create checkout directory
|
||||
run: mkdir -p ~/go/src/github.com/argoproj
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
- name: Create symlink in GOPATH
|
||||
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
|
||||
- name: Setup Golang
|
||||
@@ -150,17 +181,19 @@ jobs:
|
||||
|
||||
test-go-race:
|
||||
name: Run unit tests with -race for Go packages
|
||||
if: ${{ needs.changes.outputs.backend == 'true' }}
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- build-go
|
||||
- changes
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
|
||||
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
|
||||
steps:
|
||||
- name: Create checkout directory
|
||||
run: mkdir -p ~/go/src/github.com/argoproj
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
- name: Create symlink in GOPATH
|
||||
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
|
||||
- name: Setup Golang
|
||||
@@ -212,10 +245,13 @@ jobs:
|
||||
|
||||
codegen:
|
||||
name: Check changes to generated code
|
||||
if: ${{ needs.changes.outputs.backend == 'true' }}
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- changes
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
|
||||
with:
|
||||
@@ -260,14 +296,17 @@ jobs:
|
||||
|
||||
build-ui:
|
||||
name: Build, test & lint UI code
|
||||
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- changes
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
|
||||
with:
|
||||
node-version: '20.7.0'
|
||||
node-version: '21.6.1'
|
||||
- name: Restore node dependency cache
|
||||
id: cache-dependencies
|
||||
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
|
||||
@@ -292,15 +331,17 @@ jobs:
|
||||
|
||||
analyze:
|
||||
name: Process & analyze test artifacts
|
||||
if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' }}
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- test-go
|
||||
- build-ui
|
||||
- changes
|
||||
env:
|
||||
sonar_secret: ${{ secrets.SONAR_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Restore node dependency cache
|
||||
@@ -315,7 +356,7 @@ jobs:
|
||||
- name: Create test-results directory
|
||||
run: |
|
||||
mkdir -p test-results
|
||||
- name: Get code coverage artifiact
|
||||
- name: Get code coverage artifact
|
||||
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
|
||||
with:
|
||||
name: code-coverage
|
||||
@@ -336,35 +377,37 @@ jobs:
|
||||
SCANNER_PATH: /tmp/cache/scanner
|
||||
OS: linux
|
||||
run: |
|
||||
# We do not use the provided action, because it does contain an old
|
||||
# version of the scanner, and also takes time to build.
|
||||
set -e
|
||||
mkdir -p ${SCANNER_PATH}
|
||||
export SONAR_USER_HOME=${SCANNER_PATH}/.sonar
|
||||
if [[ ! -x "${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner" ]]; then
|
||||
curl -Ol https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SCANNER_VERSION}-${OS}.zip
|
||||
unzip -qq -o sonar-scanner-cli-${SCANNER_VERSION}-${OS}.zip -d ${SCANNER_PATH}
|
||||
fi
|
||||
|
||||
chmod +x ${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner
|
||||
chmod +x ${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/jre/bin/java
|
||||
|
||||
# Explicitly set NODE_MODULES
|
||||
export NODE_MODULES=${PWD}/ui/node_modules
|
||||
export NODE_PATH=${PWD}/ui/node_modules
|
||||
|
||||
${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner
|
||||
# We do not use the provided action, because it does contain an old
|
||||
# version of the scanner, and also takes time to build.
|
||||
set -e
|
||||
mkdir -p ${SCANNER_PATH}
|
||||
export SONAR_USER_HOME=${SCANNER_PATH}/.sonar
|
||||
if [[ ! -x "${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner" ]]; then
|
||||
curl -Ol https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SCANNER_VERSION}-${OS}.zip
|
||||
unzip -qq -o sonar-scanner-cli-${SCANNER_VERSION}-${OS}.zip -d ${SCANNER_PATH}
|
||||
fi
|
||||
|
||||
chmod +x ${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner
|
||||
chmod +x ${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/jre/bin/java
|
||||
|
||||
# Explicitly set NODE_MODULES
|
||||
export NODE_MODULES=${PWD}/ui/node_modules
|
||||
export NODE_PATH=${PWD}/ui/node_modules
|
||||
|
||||
${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner
|
||||
if: env.sonar_secret != ''
|
||||
|
||||
test-e2e:
|
||||
name: Run end-to-end tests
|
||||
if: ${{ needs.changes.outputs.backend == 'true' }}
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
k3s-version: [v1.28.2, v1.27.6, v1.26.9, v1.25.14]
|
||||
needs:
|
||||
k3s-version: [v1.29.1, v1.28.6, v1.27.10, v1.26.13, v1.25.16]
|
||||
needs:
|
||||
- build-go
|
||||
- changes
|
||||
env:
|
||||
GOPATH: /home/runner/go
|
||||
ARGOCD_FAKE_IN_CLUSTER: "true"
|
||||
@@ -377,10 +420,10 @@ jobs:
|
||||
ARGOCD_APPLICATION_NAMESPACES: "argocd-e2e-external,argocd-e2e-external-2"
|
||||
ARGOCD_SERVER: "127.0.0.1:8088"
|
||||
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
|
||||
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
|
||||
with:
|
||||
@@ -427,7 +470,7 @@ 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.37.0
|
||||
docker pull ghcr.io/dexidp/dex:v2.38.0
|
||||
docker pull argoproj/argo-cd-ci-builder:v1.0.0
|
||||
docker pull redis:7.0.14-alpine
|
||||
- name: Create target directory for binaries in the build-process
|
||||
@@ -462,3 +505,26 @@ jobs:
|
||||
name: e2e-server-k8s${{ matrix.k3s-version }}.log
|
||||
path: /tmp/e2e-server.log
|
||||
if: ${{ failure() }}
|
||||
|
||||
# workaround for status checks -- check this one job instead of each individual E2E job in the matrix
|
||||
# this allows us to skip the entire matrix when it doesn't need to run while still having accurate status checks
|
||||
# see:
|
||||
# https://github.com/argoproj/argo-workflows/pull/12006
|
||||
# https://github.com/orgs/community/discussions/9141#discussioncomment-2296809
|
||||
# https://github.com/orgs/community/discussions/26822#discussioncomment-3305794
|
||||
test-e2e-composite-result:
|
||||
name: E2E Tests - Composite result
|
||||
if: ${{ always() }}
|
||||
needs:
|
||||
- test-e2e
|
||||
- changes
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- run: |
|
||||
result="${{ needs.test-e2e.result }}"
|
||||
# mark as successful even if skipped
|
||||
if [[ $result == "success" || $result == "skipped" ]]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
2
.github/workflows/codeql.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
|
||||
# Use correct go version. https://github.com/github/codeql-action/issues/1842#issuecomment-1704398087
|
||||
- name: Setup Golang
|
||||
|
||||
16
.github/workflows/image-reuse.yaml
vendored
@@ -58,14 +58,14 @@ jobs:
|
||||
image-digest: ${{ steps.image.outputs.digest }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
if: ${{ github.ref_type == 'tag'}}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
if: ${{ github.ref_type != 'tag'}}
|
||||
|
||||
- name: Setup Golang
|
||||
@@ -74,9 +74,7 @@ jobs:
|
||||
go-version: ${{ inputs.go-version }}
|
||||
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
|
||||
with:
|
||||
cosign-release: 'v2.2.1'
|
||||
uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0
|
||||
|
||||
- uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
|
||||
- uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
|
||||
@@ -106,7 +104,7 @@ jobs:
|
||||
echo 'EOF' >> $GITHUB_ENV
|
||||
|
||||
- name: Login to Quay.io
|
||||
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
|
||||
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.quay_username }}
|
||||
@@ -114,7 +112,7 @@ jobs:
|
||||
if: ${{ inputs.quay_image_name && inputs.push }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
|
||||
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ secrets.ghcr_username }}
|
||||
@@ -122,7 +120,7 @@ jobs:
|
||||
if: ${{ inputs.ghcr_image_name && inputs.push }}
|
||||
|
||||
- name: Login to dockerhub Container Registry
|
||||
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
|
||||
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
|
||||
with:
|
||||
username: ${{ secrets.docker_username }}
|
||||
password: ${{ secrets.docker_password }}
|
||||
@@ -145,7 +143,7 @@ jobs:
|
||||
|
||||
- name: Build and push container image
|
||||
id: image
|
||||
uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 #v5.1.0
|
||||
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 #v5.3.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ inputs.platforms }}
|
||||
|
||||
6
.github/workflows/image.yaml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
image-tag: ${{ steps.image.outputs.tag}}
|
||||
platforms: ${{ steps.platforms.outputs.platforms }}
|
||||
steps:
|
||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
- uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
|
||||
- name: Set image tag for ghcr
|
||||
run: echo "tag=$(cat ./VERSION)-${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
|
||||
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
|
||||
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.7.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.10.0
|
||||
with:
|
||||
image: ghcr.io/argoproj/argo-cd/argocd
|
||||
digest: ${{ needs.build-and-publish.outputs.image-digest }}
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
- uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
- run: git clone "https://$TOKEN@github.com/argoproj/argoproj-deployments"
|
||||
env:
|
||||
TOKEN: ${{ secrets.TOKEN }}
|
||||
|
||||
2
.github/workflows/init-release.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
20
.github/workflows/release.yaml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
|
||||
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
|
||||
if: github.repository == 'argoproj/argo-cd'
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.10.0
|
||||
with:
|
||||
image: quay.io/argoproj/argocd
|
||||
digest: ${{ needs.argocd-image.outputs.image-digest }}
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -87,6 +87,14 @@ jobs:
|
||||
echo "KUBECTL_VERSION=$(go list -m k8s.io/client-go | head -n 1 | rev | cut -d' ' -f1 | rev)" >> $GITHUB_ENV
|
||||
echo "GIT_TREE_STATE=$(if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)" >> $GITHUB_ENV
|
||||
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@4d9e71b726748f254fe64fa44d273194bd18ec91
|
||||
with:
|
||||
large-packages: false
|
||||
docker-images: false
|
||||
swap-storage: false
|
||||
tool-cache: false
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0
|
||||
id: run-goreleaser
|
||||
@@ -120,7 +128,7 @@ jobs:
|
||||
contents: write # Needed for release uploads
|
||||
if: github.repository == 'argoproj/argo-cd'
|
||||
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
|
||||
with:
|
||||
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
|
||||
provenance-name: "argocd-cli.intoto.jsonl"
|
||||
@@ -139,7 +147,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -204,7 +212,7 @@ jobs:
|
||||
contents: write # Needed for release uploads
|
||||
if: github.repository == 'argoproj/argo-cd'
|
||||
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
|
||||
with:
|
||||
base64-subjects: "${{ needs.generate-sbom.outputs.hashes }}"
|
||||
provenance-name: "argocd-sbom.intoto.jsonl"
|
||||
@@ -222,7 +230,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
8
.github/workflows/scorecard.yaml
vendored
@@ -30,12 +30,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0
|
||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -62,6 +62,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@3ebbd71c74ef574dbc558c82f70e52732c8b44fe # v2.2.1
|
||||
uses: github/codeql-action/upload-sarif@83a02f7883b12e0e4e1a146174f5e2292a01e601 # v2.16.4
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
2
.github/workflows/update-snyk.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build reports
|
||||
|
||||
@@ -114,7 +114,7 @@ changelog:
|
||||
exclude:
|
||||
- '^test:'
|
||||
- '^.*?Bump(\([[:word:]]+\))?.+$'
|
||||
- '^.*?[Bot](\([[:word:]]+\))?.+$'
|
||||
- '^.*?\[Bot\](\([[:word:]]+\))?.+$'
|
||||
|
||||
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
** @argoproj/argocd-approvers
|
||||
|
||||
# Docs
|
||||
/docs/** @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
|
||||
/USERS.md @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
|
||||
/mkdocs.yml @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
|
||||
/docs/** @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
|
||||
/USERS.md @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
|
||||
/README.md @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
|
||||
/mkdocs.yml @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
|
||||
|
||||
# CI
|
||||
/.github/** @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
|
||||
|
||||
12
Dockerfile
@@ -4,9 +4,9 @@ ARG BASE_IMAGE=docker.io/library/ubuntu:22.04@sha256:0bced47fffa3361afa981854fca
|
||||
# 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.21.3@sha256:02d7116222536a5cf0fcf631f90b507758b669648e0f20186d2dc94a9b419a9b AS builder
|
||||
FROM docker.io/library/golang:1.21.9@sha256:7d0dcbe5807b1ad7272a598fbf9d7af15b5e2bed4fd6c4c2b5b3684df0b317dd AS builder
|
||||
|
||||
RUN echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list
|
||||
RUN echo 'deb http://archive.debian.org/debian buster-backports main' >> /etc/apt/sources.list
|
||||
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
openssh-server \
|
||||
@@ -28,7 +28,7 @@ WORKDIR /tmp
|
||||
COPY hack/install.sh hack/tool-versions.sh ./
|
||||
COPY hack/installers installers
|
||||
|
||||
RUN ./install.sh helm-linux && \
|
||||
RUN ./install.sh helm && \
|
||||
INSTALL_PATH=/usr/local/bin ./install.sh kustomize
|
||||
|
||||
####################################################################################################
|
||||
@@ -51,7 +51,7 @@ RUN groupadd -g $ARGOCD_USER_ID argocd && \
|
||||
apt-get update && \
|
||||
apt-get dist-upgrade -y && \
|
||||
apt-get install -y \
|
||||
git git-lfs tini gpg tzdata && \
|
||||
git git-lfs tini gpg tzdata connect-proxy && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
@@ -83,7 +83,7 @@ WORKDIR /home/argocd
|
||||
####################################################################################################
|
||||
# Argo CD UI stage
|
||||
####################################################################################################
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/node:20.6.1@sha256:14bd39208dbc0eb171cbfb26ccb9ac09fa1b2eba04ccd528ab5d12983fd9ee24 AS argocd-ui
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/node:21.6.2@sha256:65998e325b06014d4f1417a8a6afb1540d1ac66521cca76f2221a6953947f9ee AS argocd-ui
|
||||
|
||||
WORKDIR /src
|
||||
COPY ["ui/package.json", "ui/yarn.lock", "./"]
|
||||
@@ -101,7 +101,7 @@ RUN HOST_ARCH=$TARGETARCH NODE_ENV='production' NODE_ONLINE_ENV='online' NODE_OP
|
||||
####################################################################################################
|
||||
# Argo CD Build stage which performs the actual build of Argo CD binaries
|
||||
####################################################################################################
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.21.3@sha256:02d7116222536a5cf0fcf631f90b507758b669648e0f20186d2dc94a9b419a9b AS argocd-build
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.21.9@sha256:7d0dcbe5807b1ad7272a598fbf9d7af15b5e2bed4fd6c4c2b5b3684df0b317dd AS argocd-build
|
||||
|
||||
WORKDIR /go/src/github.com/argoproj/argo-cd
|
||||
|
||||
|
||||
102
Makefile
@@ -3,6 +3,7 @@ CURRENT_DIR=$(shell pwd)
|
||||
DIST_DIR=${CURRENT_DIR}/dist
|
||||
CLI_NAME=argocd
|
||||
BIN_NAME=argocd
|
||||
CGO_FLAG=0
|
||||
|
||||
GEN_RESOURCES_CLI_NAME=argocd-resources-gen
|
||||
|
||||
@@ -22,14 +23,21 @@ KUBECTL_VERSION=$(shell go list -m k8s.io/client-go | head -n 1 | rev | cut -d'
|
||||
GOPATH?=$(shell if test -x `which go`; then go env GOPATH; else echo "$(HOME)/go"; fi)
|
||||
GOCACHE?=$(HOME)/.cache/go-build
|
||||
|
||||
# Docker command to use
|
||||
DOCKER?=docker
|
||||
ifeq ($(DOCKER),podman)
|
||||
PODMAN_ARGS=--userns keep-id
|
||||
else
|
||||
PODMAN_ARGS=
|
||||
endif
|
||||
|
||||
DOCKER_SRCDIR?=$(GOPATH)/src
|
||||
DOCKER_WORKDIR?=/go/src/github.com/argoproj/argo-cd
|
||||
|
||||
ARGOCD_PROCFILE?=Procfile
|
||||
|
||||
# Strict mode has been disabled in latest versions of mkdocs-material.
|
||||
# Thus pointing to the older image of mkdocs-material matching the version used by argo-cd.
|
||||
MKDOCS_DOCKER_IMAGE?=squidfunk/mkdocs-material:4.1.1
|
||||
# pointing to python 3.7 to match https://github.com/argoproj/argo-cd/blob/master/.readthedocs.yml
|
||||
MKDOCS_DOCKER_IMAGE?=python:3.7-alpine
|
||||
MKDOCS_RUN_ARGS?=
|
||||
|
||||
# Configuration for building argocd-test-tools image
|
||||
@@ -49,7 +57,7 @@ ARGOCD_E2E_DEX_PORT?=5556
|
||||
ARGOCD_E2E_YARN_HOST?=localhost
|
||||
ARGOCD_E2E_DISABLE_AUTH?=
|
||||
|
||||
ARGOCD_E2E_TEST_TIMEOUT?=60m
|
||||
ARGOCD_E2E_TEST_TIMEOUT?=90m
|
||||
|
||||
ARGOCD_IN_CI?=false
|
||||
ARGOCD_TEST_E2E?=true
|
||||
@@ -76,7 +84,7 @@ SUDO?=
|
||||
# Runs any command in the argocd-test-utils container in server mode
|
||||
# Server mode container will start with uid 0 and drop privileges during runtime
|
||||
define run-in-test-server
|
||||
$(SUDO) docker run --rm -it \
|
||||
$(SUDO) $(DOCKER) run --rm -it \
|
||||
--name argocd-test-server \
|
||||
-u $(CONTAINER_UID):$(CONTAINER_GID) \
|
||||
-e USER_ID=$(CONTAINER_UID) \
|
||||
@@ -101,13 +109,14 @@ define run-in-test-server
|
||||
-p ${ARGOCD_E2E_APISERVER_PORT}:8080 \
|
||||
-p 4000:4000 \
|
||||
-p 5000:5000 \
|
||||
$(PODMAN_ARGS) \
|
||||
$(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \
|
||||
bash -c "$(1)"
|
||||
endef
|
||||
|
||||
# Runs any command in the argocd-test-utils container in client mode
|
||||
define run-in-test-client
|
||||
$(SUDO) docker run --rm -it \
|
||||
$(SUDO) $(DOCKER) run --rm -it \
|
||||
--name argocd-test-client \
|
||||
-u $(CONTAINER_UID):$(CONTAINER_GID) \
|
||||
-e HOME=/home/user \
|
||||
@@ -122,13 +131,14 @@ define run-in-test-client
|
||||
-v ${HOME}/.kube:/home/user/.kube${VOLUME_MOUNT} \
|
||||
-v /tmp:/tmp${VOLUME_MOUNT} \
|
||||
-w ${DOCKER_WORKDIR} \
|
||||
$(PODMAN_ARGS) \
|
||||
$(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \
|
||||
bash -c "$(1)"
|
||||
endef
|
||||
|
||||
#
|
||||
define exec-in-test-server
|
||||
$(SUDO) docker exec -it -u $(CONTAINER_UID):$(CONTAINER_GID) -e ARGOCD_E2E_RECORD=$(ARGOCD_E2E_RECORD) -e ARGOCD_E2E_K3S=$(ARGOCD_E2E_K3S) argocd-test-server $(1)
|
||||
$(SUDO) $(DOCKER) exec -it -u $(CONTAINER_UID):$(CONTAINER_GID) -e ARGOCD_E2E_RECORD=$(ARGOCD_E2E_RECORD) -e ARGOCD_E2E_K3S=$(ARGOCD_E2E_K3S) argocd-test-server $(1)
|
||||
endef
|
||||
|
||||
PATH:=$(PATH):$(PWD)/hack
|
||||
@@ -175,29 +185,21 @@ endif
|
||||
.PHONY: all
|
||||
all: cli image
|
||||
|
||||
# We have some legacy requirements for being checked out within $GOPATH.
|
||||
# The ensure-gopath target can be used as dependency to ensure we are running
|
||||
# within these boundaries.
|
||||
.PHONY: ensure-gopath
|
||||
ensure-gopath:
|
||||
ifneq ("$(PWD)","$(LEGACY_PATH)")
|
||||
@echo "Due to legacy requirements for codegen, repository needs to be checked out within \$$GOPATH"
|
||||
@echo "Location of this repo should be '$(LEGACY_PATH)' but is '$(PWD)'"
|
||||
@exit 1
|
||||
endif
|
||||
|
||||
.PHONY: gogen
|
||||
gogen: ensure-gopath
|
||||
gogen:
|
||||
export GO111MODULE=off
|
||||
go generate ./util/argo/...
|
||||
|
||||
.PHONY: protogen
|
||||
protogen: ensure-gopath mod-vendor-local
|
||||
protogen: mod-vendor-local protogen-fast
|
||||
|
||||
.PHONY: protogen-fast
|
||||
protogen-fast:
|
||||
export GO111MODULE=off
|
||||
./hack/generate-proto.sh
|
||||
|
||||
.PHONY: openapigen
|
||||
openapigen: ensure-gopath
|
||||
openapigen:
|
||||
export GO111MODULE=off
|
||||
./hack/update-openapi.sh
|
||||
|
||||
@@ -212,19 +214,22 @@ notification-docs:
|
||||
|
||||
|
||||
.PHONY: clientgen
|
||||
clientgen: ensure-gopath
|
||||
clientgen:
|
||||
export GO111MODULE=off
|
||||
./hack/update-codegen.sh
|
||||
|
||||
.PHONY: clidocsgen
|
||||
clidocsgen: ensure-gopath
|
||||
clidocsgen:
|
||||
go run tools/cmd-docs/main.go
|
||||
|
||||
|
||||
.PHONY: codegen-local
|
||||
codegen-local: ensure-gopath mod-vendor-local gogen protogen clientgen openapigen clidocsgen manifests-local notification-docs notification-catalog
|
||||
codegen-local: mod-vendor-local gogen protogen clientgen openapigen clidocsgen manifests-local notification-docs notification-catalog
|
||||
rm -rf vendor/
|
||||
|
||||
.PHONY: codegen-local-fast
|
||||
codegen-local-fast: gogen protogen-fast clientgen openapigen clidocsgen manifests-local notification-docs notification-catalog
|
||||
|
||||
.PHONY: codegen
|
||||
codegen: test-tools-image
|
||||
$(call run-in-test-client,make codegen-local)
|
||||
@@ -235,11 +240,11 @@ cli: test-tools-image
|
||||
|
||||
.PHONY: cli-local
|
||||
cli-local: clean-debug
|
||||
CGO_ENABLED=0 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${CLI_NAME} ./cmd
|
||||
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${CLI_NAME} ./cmd
|
||||
|
||||
.PHONY: gen-resources-cli-local
|
||||
gen-resources-cli-local: clean-debug
|
||||
CGO_ENABLED=0 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${GEN_RESOURCES_CLI_NAME} ./hack/gen-resources/cmd
|
||||
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${GEN_RESOURCES_CLI_NAME} ./hack/gen-resources/cmd
|
||||
|
||||
.PHONY: release-cli
|
||||
release-cli: clean-debug build-ui
|
||||
@@ -254,8 +259,8 @@ release-cli: clean-debug build-ui
|
||||
.PHONY: test-tools-image
|
||||
test-tools-image:
|
||||
ifndef SKIP_TEST_TOOLS_IMAGE
|
||||
$(SUDO) docker build --build-arg UID=$(CONTAINER_UID) -t $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) -f test/container/Dockerfile .
|
||||
$(SUDO) docker tag $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG)
|
||||
$(SUDO) $(DOCKER) build --build-arg UID=$(CONTAINER_UID) -t $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) -f test/container/Dockerfile .
|
||||
$(SUDO) $(DOCKER) tag $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG)
|
||||
endif
|
||||
|
||||
.PHONY: manifests-local
|
||||
@@ -269,25 +274,25 @@ manifests: test-tools-image
|
||||
# consolidated binary for cli, util, server, repo-server, controller
|
||||
.PHONY: argocd-all
|
||||
argocd-all: clean-debug
|
||||
CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${BIN_NAME} ./cmd
|
||||
CGO_ENABLED=${CGO_FLAG} GOOS=${GOOS} GOARCH=${GOARCH} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${BIN_NAME} ./cmd
|
||||
|
||||
.PHONY: server
|
||||
server: clean-debug
|
||||
CGO_ENABLED=0 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd
|
||||
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd
|
||||
|
||||
.PHONY: repo-server
|
||||
repo-server:
|
||||
CGO_ENABLED=0 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-repo-server ./cmd
|
||||
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-repo-server ./cmd
|
||||
|
||||
.PHONY: controller
|
||||
controller:
|
||||
CGO_ENABLED=0 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-application-controller ./cmd
|
||||
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-application-controller ./cmd
|
||||
|
||||
.PHONY: build-ui
|
||||
build-ui:
|
||||
DOCKER_BUILDKIT=1 docker build -t argocd-ui --platform=$(TARGET_ARCH) --target argocd-ui .
|
||||
DOCKER_BUILDKIT=1 $(DOCKER) build -t argocd-ui --platform=$(TARGET_ARCH) --target argocd-ui .
|
||||
find ./ui/dist -type f -not -name gitkeep -delete
|
||||
docker run -v ${CURRENT_DIR}/ui/dist/app:/tmp/app --rm -t argocd-ui sh -c 'cp -r ./dist/app/* /tmp/app/'
|
||||
$(DOCKER) run -v ${CURRENT_DIR}/ui/dist/app:/tmp/app --rm -t argocd-ui sh -c 'cp -r ./dist/app/* /tmp/app/'
|
||||
|
||||
.PHONY: image
|
||||
ifeq ($(DEV_IMAGE), true)
|
||||
@@ -296,29 +301,29 @@ ifeq ($(DEV_IMAGE), true)
|
||||
# the dist directory is under .dockerignore.
|
||||
IMAGE_TAG="dev-$(shell git describe --always --dirty)"
|
||||
image: build-ui
|
||||
DOCKER_BUILDKIT=1 docker build --platform=$(TARGET_ARCH) -t argocd-base --target argocd-base .
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd ./cmd
|
||||
DOCKER_BUILDKIT=1 $(DOCKER) build --platform=$(TARGET_ARCH) -t argocd-base --target argocd-base .
|
||||
CGO_ENABLED=${CGO_FLAG} GOOS=linux GOARCH=amd64 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd ./cmd
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-server
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-application-controller
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-repo-server
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-cmp-server
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-dex
|
||||
cp Dockerfile.dev dist
|
||||
DOCKER_BUILDKIT=1 docker build --platform=$(TARGET_ARCH) -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) -f dist/Dockerfile.dev dist
|
||||
DOCKER_BUILDKIT=1 $(DOCKER) build --platform=$(TARGET_ARCH) -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) -f dist/Dockerfile.dev dist
|
||||
else
|
||||
image:
|
||||
DOCKER_BUILDKIT=1 docker build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) --platform=$(TARGET_ARCH) .
|
||||
DOCKER_BUILDKIT=1 $(DOCKER) build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) --platform=$(TARGET_ARCH) .
|
||||
endif
|
||||
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) ; fi
|
||||
@if [ "$(DOCKER_PUSH)" = "true" ] ; then $(DOCKER) push $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) ; fi
|
||||
|
||||
.PHONY: armimage
|
||||
armimage:
|
||||
docker build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG)-arm .
|
||||
$(DOCKER) build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG)-arm .
|
||||
|
||||
.PHONY: builder-image
|
||||
builder-image:
|
||||
docker build -t $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) --target builder .
|
||||
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) ; fi
|
||||
$(DOCKER) build -t $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) --target builder .
|
||||
@if [ "$(DOCKER_PUSH)" = "true" ] ; then $(DOCKER) push $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) ; fi
|
||||
|
||||
.PHONY: mod-download
|
||||
mod-download: test-tools-image
|
||||
@@ -429,7 +434,7 @@ debug-test-client: test-tools-image
|
||||
# Starts e2e server in a container
|
||||
.PHONY: start-e2e
|
||||
start-e2e: test-tools-image
|
||||
docker version
|
||||
$(DOCKER) version
|
||||
mkdir -p ${GOCACHE}
|
||||
$(call run-in-test-server,make ARGOCD_PROCFILE=test/container/Procfile start-e2e-local)
|
||||
|
||||
@@ -476,7 +481,7 @@ clean: clean-debug
|
||||
|
||||
.PHONY: start
|
||||
start: test-tools-image
|
||||
docker version
|
||||
$(DOCKER) version
|
||||
$(call run-in-test-server,make ARGOCD_PROCFILE=test/container/Procfile start-local ARGOCD_START=${ARGOCD_START})
|
||||
|
||||
# Starts a local instance of ArgoCD
|
||||
@@ -526,7 +531,7 @@ build-docs-local:
|
||||
|
||||
.PHONY: build-docs
|
||||
build-docs:
|
||||
docker run ${MKDOCS_RUN_ARGS} --rm -it -v ${CURRENT_DIR}:/docs --entrypoint "" ${MKDOCS_DOCKER_IMAGE} sh -c 'pip install -r docs/requirements.txt; mkdocs build'
|
||||
$(DOCKER) run ${MKDOCS_RUN_ARGS} --rm -it -v ${CURRENT_DIR}:/docs -w /docs --entrypoint "" ${MKDOCS_DOCKER_IMAGE} sh -c 'pip install -r docs/requirements.txt; mkdocs build'
|
||||
|
||||
.PHONY: serve-docs-local
|
||||
serve-docs-local:
|
||||
@@ -534,8 +539,7 @@ serve-docs-local:
|
||||
|
||||
.PHONY: serve-docs
|
||||
serve-docs:
|
||||
docker run ${MKDOCS_RUN_ARGS} --rm -it -p 8000:8000 -v ${CURRENT_DIR}/site:/site -w /site --entrypoint "" ${MKDOCS_DOCKER_IMAGE} python3 -m http.server --bind 0.0.0.0 8000
|
||||
|
||||
$(DOCKER) run ${MKDOCS_RUN_ARGS} --rm -it -p 8000:8000 -v ${CURRENT_DIR}:/docs -w /docs --entrypoint "" ${MKDOCS_DOCKER_IMAGE} sh -c 'pip install -r docs/requirements.txt; mkdocs serve -a $$(ip route get 1 | awk '\''{print $$7}'\''):8000'
|
||||
|
||||
# Verify that kubectl can connect to your K8s cluster from Docker
|
||||
.PHONY: verify-kube-connect
|
||||
@@ -558,7 +562,7 @@ install-tools-local: install-test-tools-local install-codegen-tools-local instal
|
||||
.PHONY: install-test-tools-local
|
||||
install-test-tools-local:
|
||||
./hack/install.sh kustomize
|
||||
./hack/install.sh helm-linux
|
||||
./hack/install.sh helm
|
||||
./hack/install.sh gotestsum
|
||||
|
||||
# Installs all tools required for running codegen (Linux packages)
|
||||
@@ -587,7 +591,7 @@ list:
|
||||
|
||||
.PHONY: applicationset-controller
|
||||
applicationset-controller:
|
||||
GODEBUG="tarinsecurepath=0,zipinsecurepath=0" CGO_ENABLED=0 go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-applicationset-controller ./cmd
|
||||
GODEBUG="tarinsecurepath=0,zipinsecurepath=0" CGO_ENABLED=${CGO_FLAG} go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-applicationset-controller ./cmd
|
||||
|
||||
.PHONY: checksums
|
||||
checksums:
|
||||
|
||||
2
Procfile
@@ -1,4 +1,4 @@
|
||||
controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} --server-side-diff-enabled=${ARGOCD_APPLICATION_CONTROLLER_SERVER_SIDE_DIFF:-'false'}"
|
||||
controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "HOSTNAME=testappcontroller-1 FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} --server-side-diff-enabled=${ARGOCD_APPLICATION_CONTROLLER_SERVER_SIDE_DIFF:-'false'}"
|
||||
api-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''}"
|
||||
dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v2/cmd gendexcfg -o `pwd`/dist/dex.yaml && (test -f dist/dex.yaml || { echo 'Failed to generate dex configuration'; exit 1; }) && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:$(grep "image: ghcr.io/dexidp/dex" manifests/base/dex/argocd-dex-server-deployment.yaml | cut -d':' -f3) dex serve /dex.yaml"
|
||||
redis: bash -c "if [ \"$ARGOCD_REDIS_LOCAL\" = 'true' ]; then redis-server --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; else docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} docker.io/library/redis:$(grep "image: redis" manifests/base/redis/argocd-redis-deployment.yaml | cut -d':' -f3) --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; fi"
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
**Social:**
|
||||
[](https://twitter.com/argoproj)
|
||||
[](https://argoproj.github.io/community/join-slack)
|
||||
[](https://www.linkedin.com/company/argoproj/)
|
||||
|
||||
# Argo CD - Declarative Continuous Delivery for Kubernetes
|
||||
|
||||
@@ -85,4 +86,5 @@ 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)
|
||||
1. [How to create Argo CD Applications Automatically using ApplicationSet? "Automation of GitOps"](https://amralaayassen.medium.com/how-to-create-argocd-applications-automatically-using-applicationset-automation-of-the-gitops-59455eaf4f72)
|
||||
1. [Progressive Delivery with Service Mesh – Argo Rollouts with Istio](https://www.cncf.io/blog/2022/12/16/progressive-delivery-with-service-mesh-argo-rollouts-with-istio/)
|
||||
|
||||
|
||||
22
USERS.md
@@ -20,10 +20,12 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Allianz Direct](https://www.allianzdirect.de/)
|
||||
1. [Amadeus IT Group](https://amadeus.com/)
|
||||
1. [Ambassador Labs](https://www.getambassador.io/)
|
||||
1. [Ancestry](https://www.ancestry.com/)
|
||||
1. [ANSTO - Australian Synchrotron](https://www.synchrotron.org.au/)
|
||||
1. [Ant Group](https://www.antgroup.com/)
|
||||
1. [AppDirect](https://www.appdirect.com)
|
||||
1. [Arctiq Inc.](https://www.arctiq.ca)
|
||||
2. [Arturia](https://www.arturia.com)
|
||||
1. [ARZ Allgemeines Rechenzentrum GmbH](https://www.arz.at/)
|
||||
1. [Autodesk](https://www.autodesk.com)
|
||||
1. [Axians ACSP](https://www.axians.fr)
|
||||
@@ -40,17 +42,18 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Boozt](https://www.booztgroup.com/)
|
||||
1. [Boticario](https://www.boticario.com.br/)
|
||||
1. [Bulder Bank](https://bulderbank.no)
|
||||
1. [CAM](https://cam-inc.co.jp)
|
||||
1. [Camptocamp](https://camptocamp.com)
|
||||
1. [Candis](https://www.candis.io)
|
||||
1. [Capital One](https://www.capitalone.com)
|
||||
1. [CARFAX](https://www.carfax.com)
|
||||
1. [CARFAX Europe](https://www.carfax.eu)
|
||||
1. [CARFAX](https://www.carfax.com)
|
||||
1. [Carrefour Group](https://www.carrefour.com)
|
||||
1. [Casavo](https://casavo.com)
|
||||
1. [Celonis](https://www.celonis.com/)
|
||||
1. [CERN](https://home.cern/)
|
||||
1. [Chargetrip](https://chargetrip.com)
|
||||
1. [Chainnodes](https://chainnodes.org)
|
||||
1. [Chargetrip](https://chargetrip.com)
|
||||
1. [Chime](https://www.chime.com)
|
||||
1. [Cisco ET&I](https://eti.cisco.com/)
|
||||
1. [Cloud Posse](https://www.cloudposse.com/)
|
||||
@@ -93,6 +96,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Fave](https://myfave.com)
|
||||
1. [Flexport](https://www.flexport.com/)
|
||||
1. [Flip](https://flip.id)
|
||||
1. [Fly Security](https://www.flysecurity.com.br/)
|
||||
1. [Fonoa](https://www.fonoa.com/)
|
||||
1. [Fortra](https://www.fortra.com)
|
||||
1. [freee](https://corp.freee.co.jp/en/company/)
|
||||
@@ -111,8 +115,8 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [GlueOps](https://glueops.dev)
|
||||
1. [GMETRI](https://gmetri.com/)
|
||||
1. [Gojek](https://www.gojek.io/)
|
||||
1. [GoTo](https://www.goto.com/)
|
||||
1. [GoTo Financial](https://gotofinancial.com/)
|
||||
1. [GoTo](https://www.goto.com/)
|
||||
1. [Greenpass](https://www.greenpass.com.br/)
|
||||
1. [Gridfuse](https://gridfuse.com/)
|
||||
1. [Groww](https://groww.in)
|
||||
@@ -125,9 +129,11 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Hiya](https://hiya.com)
|
||||
1. [Honestbank](https://honestbank.com)
|
||||
1. [Hostinger](https://www.hostinger.com)
|
||||
1. [IABAI](https://www.iab.ai)
|
||||
1. [IBM](https://www.ibm.com/)
|
||||
1. [Ibotta](https://home.ibotta.com)
|
||||
1. [IITS-Consulting](https://iits-consulting.de)
|
||||
1. [IllumiDesk](https://www.illumidesk.com)
|
||||
1. [imaware](https://imaware.health)
|
||||
1. [Indeed](https://indeed.com)
|
||||
1. [Index Exchange](https://www.indexexchange.com/)
|
||||
@@ -185,6 +191,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Natura &Co](https://naturaeco.com/)
|
||||
1. [Nethopper](https://nethopper.io)
|
||||
1. [New Relic](https://newrelic.com/)
|
||||
1. [Nextbasket](https://nextbasket.com)
|
||||
1. [Nextdoor](https://nextdoor.com/)
|
||||
1. [Nikkei](https://www.nikkei.co.jp/nikkeiinfo/en/)
|
||||
1. [Nitro](https://gonitro.com)
|
||||
@@ -195,6 +202,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Olfeo](https://www.olfeo.com/)
|
||||
1. [omegaUp](https://omegaUp.com)
|
||||
1. [Omni](https://omni.se/)
|
||||
1. [Oncourse Home Solutions](https://oncoursehome.com/)
|
||||
1. [openEuler](https://openeuler.org)
|
||||
1. [openGauss](https://opengauss.org/)
|
||||
1. [OpenGov](https://opengov.com)
|
||||
@@ -211,6 +219,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [PagerDuty](https://www.pagerduty.com/)
|
||||
1. [Pandosearch](https://www.pandosearch.com/en/home)
|
||||
1. [Patreon](https://www.patreon.com/)
|
||||
1. [PayIt](https://payitgov.com/)
|
||||
1. [PayPay](https://paypay.ne.jp/)
|
||||
1. [Peloton Interactive](https://www.onepeloton.com/)
|
||||
1. [Percona](https://percona.com/)
|
||||
@@ -218,6 +227,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Pigment](https://www.gopigment.com/)
|
||||
1. [Pipefy](https://www.pipefy.com/)
|
||||
1. [Pismo](https://pismo.io/)
|
||||
1. [PITS Globale Datenrettungsdienste](https://www.pitsdatenrettung.de/)
|
||||
1. [Platform9 Systems](https://platform9.com/)
|
||||
1. [Polarpoint.io](https://polarpoint.io)
|
||||
1. [PostFinance](https://github.com/postfinance)
|
||||
@@ -233,15 +243,18 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [QuintoAndar](https://quintoandar.com.br)
|
||||
1. [Quipper](https://www.quipper.com/)
|
||||
1. [RapidAPI](https://www.rapidapi.com/)
|
||||
1. [rebuy](https://www.rebuy.de/)
|
||||
1. [Recreation.gov](https://www.recreation.gov/)
|
||||
1. [Red Hat](https://www.redhat.com/)
|
||||
1. [Redpill Linpro](https://www.redpill-linpro.com/)
|
||||
1. [Reenigne Cloud](https://reenigne.ca)
|
||||
1. [reev.com](https://www.reev.com/)
|
||||
1. [RightRev](https://rightrev.com/)
|
||||
1. [Rijkswaterstaat](https://www.rijkswaterstaat.nl/en)
|
||||
1. [Rise](https://www.risecard.eu/)
|
||||
1. [Riskified](https://www.riskified.com/)
|
||||
1. [Robotinfra](https://www.robotinfra.com)
|
||||
1. [Rocket.Chat](https://rocket.chat)
|
||||
1. [Rubin Observatory](https://www.lsst.org)
|
||||
1. [Saildrone](https://www.saildrone.com/)
|
||||
1. [Salad Technologies](https://salad.com/)
|
||||
@@ -252,6 +265,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [SCRM Lidl International Hub](https://scrm.lidl)
|
||||
1. [SEEK](https://seek.com.au)
|
||||
1. [Semgrep](https://semgrep.com)
|
||||
1. [Shield](https://shield.com)
|
||||
1. [SI Analytics](https://si-analytics.ai)
|
||||
1. [Skit](https://skit.ai/)
|
||||
1. [Skyscanner](https://www.skyscanner.net/)
|
||||
@@ -267,6 +281,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Splunk](https://splunk.com/)
|
||||
1. [Spores Labs](https://spores.app)
|
||||
1. [Statsig](https://statsig.com)
|
||||
1. [SternumIOT](https://sternumiot.com)
|
||||
1. [StreamNative](https://streamnative.io)
|
||||
1. [Stuart](https://stuart.com/)
|
||||
1. [Sumo Logic](https://sumologic.com/)
|
||||
@@ -280,6 +295,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Tamkeen Technologies](https://tamkeentech.sa/)
|
||||
1. [Techcombank](https://www.techcombank.com.vn/trang-chu)
|
||||
1. [Technacy](https://www.technacy.it/)
|
||||
1. [Telavita](https://www.telavita.com.br/)
|
||||
1. [Tesla](https://tesla.com/)
|
||||
1. [The Scale Factory](https://www.scalefactory.com/)
|
||||
1. [ThousandEyes](https://www.thousandeyes.com/)
|
||||
|
||||
@@ -17,9 +17,11 @@ package controllers
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
log "github.com/sirupsen/logrus"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierr "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -50,6 +52,7 @@ import (
|
||||
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
|
||||
argoutil "github.com/argoproj/argo-cd/v2/util/argo"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
|
||||
)
|
||||
@@ -124,18 +127,20 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
|
||||
// Log a warning if there are unrecognized generators
|
||||
_ = utils.CheckInvalidGenerators(&applicationSetInfo)
|
||||
// desiredApplications is the main list of all expected Applications from all generators in this appset.
|
||||
desiredApplications, applicationSetReason, err := r.generateApplications(logCtx, applicationSetInfo)
|
||||
if err != nil {
|
||||
desiredApplications, applicationSetReason, generatorsErr := r.generateApplications(logCtx, applicationSetInfo)
|
||||
if generatorsErr != nil {
|
||||
_ = r.setApplicationSetStatusCondition(ctx,
|
||||
&applicationSetInfo,
|
||||
argov1alpha1.ApplicationSetCondition{
|
||||
Type: argov1alpha1.ApplicationSetConditionErrorOccurred,
|
||||
Message: err.Error(),
|
||||
Message: generatorsErr.Error(),
|
||||
Reason: string(applicationSetReason),
|
||||
Status: argov1alpha1.ApplicationSetConditionStatusTrue,
|
||||
}, parametersGenerated,
|
||||
)
|
||||
return ctrl.Result{}, err
|
||||
if len(desiredApplications) < 1 {
|
||||
return ctrl.Result{}, generatorsErr
|
||||
}
|
||||
}
|
||||
|
||||
parametersGenerated = true
|
||||
@@ -309,7 +314,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
|
||||
|
||||
requeueAfter := r.getMinRequeueAfter(&applicationSetInfo)
|
||||
|
||||
if len(validateErrors) == 0 {
|
||||
if len(validateErrors) == 0 && generatorsErr == nil {
|
||||
if err := r.setApplicationSetStatusCondition(ctx,
|
||||
&applicationSetInfo,
|
||||
argov1alpha1.ApplicationSetCondition{
|
||||
@@ -666,7 +671,7 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
|
||||
},
|
||||
}
|
||||
|
||||
action, err := utils.CreateOrUpdate(ctx, appLog, r.Client, applicationSet.Spec.IgnoreApplicationDifferences, found, func() error {
|
||||
action, err := utils.CreateOrUpdate(ctx, appLog, r.Client, applicationSet.Spec.IgnoreApplicationDifferences, normalizers.IgnoreNormalizerOpts{}, found, func() error {
|
||||
// Copy only the Application/ObjectMeta fields that are significant, from the generatedApp
|
||||
found.Spec = generatedApp.Spec
|
||||
|
||||
@@ -714,6 +719,17 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve post-delete finalizers:
|
||||
// https://github.com/argoproj/argo-cd/issues/17181
|
||||
for _, finalizer := range found.ObjectMeta.Finalizers {
|
||||
if strings.HasPrefix(finalizer, argov1alpha1.PostDeleteFinalizerName) {
|
||||
if generatedApp.Finalizers == nil {
|
||||
generatedApp.Finalizers = []string{}
|
||||
}
|
||||
generatedApp.Finalizers = append(generatedApp.Finalizers, finalizer)
|
||||
}
|
||||
}
|
||||
|
||||
found.ObjectMeta.Annotations = generatedApp.Annotations
|
||||
|
||||
found.ObjectMeta.Finalizers = generatedApp.Finalizers
|
||||
@@ -1526,10 +1542,14 @@ func shouldRequeueApplicationSet(appOld *argov1alpha1.Application, appNew *argov
|
||||
}
|
||||
|
||||
// the applicationset controller owns the application spec, labels, annotations, and finalizers on the applications
|
||||
if !reflect.DeepEqual(appOld.Spec, appNew.Spec) ||
|
||||
!reflect.DeepEqual(appOld.ObjectMeta.GetAnnotations(), appNew.ObjectMeta.GetAnnotations()) ||
|
||||
!reflect.DeepEqual(appOld.ObjectMeta.GetLabels(), appNew.ObjectMeta.GetLabels()) ||
|
||||
!reflect.DeepEqual(appOld.ObjectMeta.GetFinalizers(), appNew.ObjectMeta.GetFinalizers()) {
|
||||
// reflect.DeepEqual considers nil slices/maps not equal to empty slices/maps
|
||||
// https://pkg.go.dev/reflect#DeepEqual
|
||||
// ApplicationDestination has an unexported field so we can just use the == for comparsion
|
||||
if !cmp.Equal(appOld.Spec, appNew.Spec, cmpopts.EquateEmpty(), cmpopts.EquateComparable(argov1alpha1.ApplicationDestination{})) ||
|
||||
!cmp.Equal(appOld.ObjectMeta.GetAnnotations(), appNew.ObjectMeta.GetAnnotations(), cmpopts.EquateEmpty()) ||
|
||||
!cmp.Equal(appOld.ObjectMeta.GetLabels(), appNew.ObjectMeta.GetLabels(), cmpopts.EquateEmpty()) ||
|
||||
!cmp.Equal(appOld.ObjectMeta.GetFinalizers(), appNew.ObjectMeta.GetFinalizers(), cmpopts.EquateEmpty()) {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1281,6 +1281,71 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "Ensure that argocd post-delete finalizers are preserved from an existing app",
|
||||
appSet: v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Template: v1alpha1.ApplicationSetTemplate{
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Project: "project",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
existingApps: []v1alpha1.Application{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: application.ApplicationKind,
|
||||
APIVersion: "argoproj.io/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app1",
|
||||
Namespace: "namespace",
|
||||
ResourceVersion: "2",
|
||||
Finalizers: []string{
|
||||
v1alpha1.PostDeleteFinalizerName,
|
||||
v1alpha1.PostDeleteFinalizerName + "/mystage",
|
||||
},
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Project: "project",
|
||||
},
|
||||
},
|
||||
},
|
||||
desiredApps: []v1alpha1.Application{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app1",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Project: "project",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []v1alpha1.Application{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: application.ApplicationKind,
|
||||
APIVersion: "argoproj.io/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app1",
|
||||
Namespace: "namespace",
|
||||
ResourceVersion: "2",
|
||||
Finalizers: []string{
|
||||
v1alpha1.PostDeleteFinalizerName,
|
||||
v1alpha1.PostDeleteFinalizerName + "/mystage",
|
||||
},
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Project: "project",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
|
||||
@@ -2423,6 +2488,91 @@ func TestReconcilerValidationProjectErrorBehaviour(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestReconcilerCreateAppsRecoveringRenderError(t *testing.T) {
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
err := v1alpha1.AddToScheme(scheme)
|
||||
assert.Nil(t, err)
|
||||
err = v1alpha1.AddToScheme(scheme)
|
||||
assert.Nil(t, err)
|
||||
|
||||
project := v1alpha1.AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"},
|
||||
}
|
||||
appSet := v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
GoTemplate: true,
|
||||
Generators: []v1alpha1.ApplicationSetGenerator{
|
||||
{
|
||||
List: &v1alpha1.ListGenerator{
|
||||
Elements: []apiextensionsv1.JSON{{
|
||||
Raw: []byte(`{"name": "very-good-app"}`),
|
||||
}, {
|
||||
Raw: []byte(`{"name": "bad-app"}`),
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
Template: v1alpha1.ApplicationSetTemplate{
|
||||
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
|
||||
Name: "{{ index (splitList \"-\" .name ) 2 }}",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Source: &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"},
|
||||
Project: "default",
|
||||
Destination: v1alpha1.ApplicationDestination{Server: "https://kubernetes.default.svc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
kubeclientset := kubefake.NewSimpleClientset()
|
||||
argoDBMock := dbmocks.ArgoDB{}
|
||||
argoObjs := []runtime.Object{&project}
|
||||
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
|
||||
|
||||
r := ApplicationSetReconciler{
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Renderer: &utils.Render{},
|
||||
Recorder: record.NewFakeRecorder(1),
|
||||
Cache: &fakeCache{},
|
||||
Generators: map[string]generators.Generator{
|
||||
"List": generators.NewListGenerator(),
|
||||
},
|
||||
ArgoDB: &argoDBMock,
|
||||
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
|
||||
KubeClientset: kubeclientset,
|
||||
Policy: v1alpha1.ApplicationsSyncPolicySync,
|
||||
ArgoCDNamespace: "argocd",
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "argocd",
|
||||
Name: "name",
|
||||
},
|
||||
}
|
||||
|
||||
// Verify that on generatorsError, no error is returned, but the object is requeued
|
||||
res, err := r.Reconcile(context.Background(), req)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, res.RequeueAfter == ReconcileRequeueOnValidationError)
|
||||
|
||||
var app v1alpha1.Application
|
||||
|
||||
// make sure good app got created
|
||||
err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "app"}, &app)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, app.Name, "app")
|
||||
}
|
||||
|
||||
func TestSetApplicationSetStatusCondition(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
err := v1alpha1.AddToScheme(scheme)
|
||||
@@ -6087,14 +6237,70 @@ func TestOwnsHandler(t *testing.T) {
|
||||
ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}},
|
||||
ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"bar": "foo"}}},
|
||||
}}, want: true},
|
||||
{name: "DifferentApplicationLabelsNil", args: args{e: event.UpdateEvent{
|
||||
ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{}}},
|
||||
ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: nil}},
|
||||
}}, want: false},
|
||||
{name: "DifferentApplicationAnnotations", args: args{e: event.UpdateEvent{
|
||||
ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"foo": "bar"}}},
|
||||
ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"bar": "foo"}}},
|
||||
}}, want: true},
|
||||
{name: "DifferentApplicationAnnotationsNil", args: args{e: event.UpdateEvent{
|
||||
ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}}},
|
||||
ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: nil}},
|
||||
}}, want: false},
|
||||
{name: "DifferentApplicationFinalizers", args: args{e: event.UpdateEvent{
|
||||
ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"argo"}}},
|
||||
ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"none"}}},
|
||||
}}, want: true},
|
||||
{name: "DifferentApplicationFinalizersNil", args: args{e: event.UpdateEvent{
|
||||
ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{}}},
|
||||
ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: nil}},
|
||||
}}, want: false},
|
||||
{name: "ApplicationDestinationSame", args: args{e: event.UpdateEvent{
|
||||
ObjectOld: &v1alpha1.Application{
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Destination: v1alpha1.ApplicationDestination{
|
||||
Server: "server",
|
||||
Namespace: "ns",
|
||||
Name: "name",
|
||||
},
|
||||
},
|
||||
},
|
||||
ObjectNew: &v1alpha1.Application{
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Destination: v1alpha1.ApplicationDestination{
|
||||
Server: "server",
|
||||
Namespace: "ns",
|
||||
Name: "name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
enableProgressiveSyncs: true,
|
||||
}, want: false},
|
||||
{name: "ApplicationDestinationDiff", args: args{e: event.UpdateEvent{
|
||||
ObjectOld: &v1alpha1.Application{
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Destination: v1alpha1.ApplicationDestination{
|
||||
Server: "server",
|
||||
Namespace: "ns",
|
||||
Name: "name",
|
||||
},
|
||||
},
|
||||
},
|
||||
ObjectNew: &v1alpha1.Application{
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Destination: v1alpha1.ApplicationDestination{
|
||||
Server: "notSameServer",
|
||||
Namespace: "ns",
|
||||
Name: "name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
enableProgressiveSyncs: true,
|
||||
}, want: true},
|
||||
{name: "NotAnAppOld", args: args{e: event.UpdateEvent{
|
||||
ObjectOld: &v1alpha1.AppProject{},
|
||||
ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"bar": "foo"}}},
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
argodiff "github.com/argoproj/argo-cd/v2/util/argo/diff"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
)
|
||||
|
||||
// CreateOrUpdate overrides "sigs.k8s.io/controller-runtime" function
|
||||
@@ -35,7 +36,7 @@ import (
|
||||
// The MutateFn is called regardless of creating or updating an object.
|
||||
//
|
||||
// It returns the executed operation and an error.
|
||||
func CreateOrUpdate(ctx context.Context, logCtx *log.Entry, c client.Client, ignoreAppDifferences argov1alpha1.ApplicationSetIgnoreDifferences, obj *argov1alpha1.Application, f controllerutil.MutateFn) (controllerutil.OperationResult, error) {
|
||||
func CreateOrUpdate(ctx context.Context, logCtx *log.Entry, c client.Client, ignoreAppDifferences argov1alpha1.ApplicationSetIgnoreDifferences, ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts, obj *argov1alpha1.Application, f controllerutil.MutateFn) (controllerutil.OperationResult, error) {
|
||||
|
||||
key := client.ObjectKeyFromObject(obj)
|
||||
if err := c.Get(ctx, key, obj); err != nil {
|
||||
@@ -60,7 +61,7 @@ func CreateOrUpdate(ctx context.Context, logCtx *log.Entry, c client.Client, ign
|
||||
|
||||
// Apply ignoreApplicationDifferences rules to remove ignored fields from both the live and the desired state. This
|
||||
// prevents those differences from appearing in the diff and therefore in the patch.
|
||||
err := applyIgnoreDifferences(ignoreAppDifferences, normalizedLive, obj)
|
||||
err := applyIgnoreDifferences(ignoreAppDifferences, normalizedLive, obj, ignoreNormalizerOpts)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, fmt.Errorf("failed to apply ignore differences: %w", err)
|
||||
}
|
||||
@@ -134,14 +135,14 @@ func mutate(f controllerutil.MutateFn, key client.ObjectKey, obj client.Object)
|
||||
}
|
||||
|
||||
// applyIgnoreDifferences applies the ignore differences rules to the found application. It modifies the applications in place.
|
||||
func applyIgnoreDifferences(applicationSetIgnoreDifferences argov1alpha1.ApplicationSetIgnoreDifferences, found *argov1alpha1.Application, generatedApp *argov1alpha1.Application) error {
|
||||
func applyIgnoreDifferences(applicationSetIgnoreDifferences argov1alpha1.ApplicationSetIgnoreDifferences, found *argov1alpha1.Application, generatedApp *argov1alpha1.Application, ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts) error {
|
||||
if len(applicationSetIgnoreDifferences) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
generatedAppCopy := generatedApp.DeepCopy()
|
||||
diffConfig, err := argodiff.NewDiffConfigBuilder().
|
||||
WithDiffSettings(applicationSetIgnoreDifferences.ToApplicationIgnoreDifferences(), nil, false).
|
||||
WithDiffSettings(applicationSetIgnoreDifferences.ToApplicationIgnoreDifferences(), nil, false, ignoreNormalizerOpts).
|
||||
WithNoCache().
|
||||
Build()
|
||||
if err != nil {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
)
|
||||
|
||||
func Test_applyIgnoreDifferences(t *testing.T) {
|
||||
@@ -222,7 +223,7 @@ spec:
|
||||
generatedApp := v1alpha1.Application{TypeMeta: appMeta}
|
||||
err = yaml.Unmarshal([]byte(tc.generatedApp), &generatedApp)
|
||||
require.NoError(t, err, tc.generatedApp)
|
||||
err = applyIgnoreDifferences(tc.ignoreDifferences, &foundApp, &generatedApp)
|
||||
err = applyIgnoreDifferences(tc.ignoreDifferences, &foundApp, &generatedApp, normalizers.IgnoreNormalizerOpts{})
|
||||
require.NoError(t, err)
|
||||
yamlFound, err := yaml.Marshal(tc.foundApp)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 27 KiB |
@@ -975,6 +975,25 @@
|
||||
"type": "string",
|
||||
"name": "project",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
"collectionFormat": "multi",
|
||||
"name": "sourcePositions",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "multi",
|
||||
"name": "revisions",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -4219,6 +4238,19 @@
|
||||
"revision": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"sourcePositions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
}
|
||||
},
|
||||
"strategy": {
|
||||
"$ref": "#/definitions/v1alpha1SyncStrategy"
|
||||
},
|
||||
@@ -5664,6 +5696,10 @@
|
||||
"type": "string",
|
||||
"title": "ClusterName contains AWS cluster name"
|
||||
},
|
||||
"profile": {
|
||||
"description": "Profile contains optional role ARN. If set then AWS IAM Authenticator uses the profile to perform cluster operations instead of the default AWS credential provider chain.",
|
||||
"type": "string"
|
||||
},
|
||||
"roleARN": {
|
||||
"description": "RoleARN contains optional role ARN. If set then AWS IAM Authenticator assume a role to perform cluster operations instead of the default AWS credential provider chain.",
|
||||
"type": "string"
|
||||
@@ -6420,6 +6456,10 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"labelWithoutSelector": {
|
||||
"type": "boolean",
|
||||
"title": "LabelWithoutSelector specifies whether to apply common labels to resource selectors or not"
|
||||
},
|
||||
"namePrefix": {
|
||||
"type": "string",
|
||||
"title": "NamePrefix is a prefix appended to resources for Kustomize apps"
|
||||
@@ -7405,6 +7445,7 @@
|
||||
"properties": {
|
||||
"elements": {
|
||||
"type": "array",
|
||||
"title": "+kubebuilder:validation:Optional",
|
||||
"items": {
|
||||
"$ref": "#/definitions/v1JSON"
|
||||
}
|
||||
@@ -8499,6 +8540,9 @@
|
||||
"format": "int64",
|
||||
"title": "ID is an auto incrementing identifier of the RevisionHistory"
|
||||
},
|
||||
"initiatedBy": {
|
||||
"$ref": "#/definitions/v1alpha1OperationInitiator"
|
||||
},
|
||||
"revision": {
|
||||
"type": "string",
|
||||
"title": "Revision holds the revision the sync was performed against"
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/ratelimiter"
|
||||
"github.com/argoproj/pkg/stats"
|
||||
"github.com/redis/go-redis/v9"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -20,19 +19,18 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/controller/sharding"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/ratelimiter"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
|
||||
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
|
||||
"github.com/argoproj/argo-cd/v2/util/cli"
|
||||
"github.com/argoproj/argo-cd/v2/util/db"
|
||||
"github.com/argoproj/argo-cd/v2/util/env"
|
||||
"github.com/argoproj/argo-cd/v2/util/errors"
|
||||
kubeutil "github.com/argoproj/argo-cd/v2/util/kube"
|
||||
"github.com/argoproj/argo-cd/v2/util/settings"
|
||||
"github.com/argoproj/argo-cd/v2/util/tls"
|
||||
"github.com/argoproj/argo-cd/v2/util/trace"
|
||||
kubeerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -50,6 +48,7 @@ func NewCommand() *cobra.Command {
|
||||
clientConfig clientcmd.ClientConfig
|
||||
appResyncPeriod int64
|
||||
appHardResyncPeriod int64
|
||||
appResyncJitter int64
|
||||
repoErrorGracePeriod int64
|
||||
repoServerAddress string
|
||||
repoServerTimeoutSeconds int
|
||||
@@ -74,6 +73,7 @@ func NewCommand() *cobra.Command {
|
||||
shardingAlgorithm string
|
||||
enableDynamicClusterDistribution bool
|
||||
serverSideDiff bool
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
|
||||
)
|
||||
var command = cobra.Command{
|
||||
Use: cliName,
|
||||
@@ -146,7 +146,8 @@ func NewCommand() *cobra.Command {
|
||||
appController.InvalidateProjectsCache()
|
||||
}))
|
||||
kubectl := kubeutil.NewKubectl()
|
||||
clusterFilter := getClusterFilter(kubeClient, settingsMgr, shardingAlgorithm, enableDynamicClusterDistribution)
|
||||
clusterSharding, err := sharding.GetClusterSharding(kubeClient, settingsMgr, shardingAlgorithm, enableDynamicClusterDistribution)
|
||||
errors.CheckError(err)
|
||||
appController, err = controller.NewApplicationController(
|
||||
namespace,
|
||||
settingsMgr,
|
||||
@@ -157,6 +158,7 @@ func NewCommand() *cobra.Command {
|
||||
kubectl,
|
||||
resyncDuration,
|
||||
hardResyncDuration,
|
||||
time.Duration(appResyncJitter)*time.Second,
|
||||
time.Duration(selfHealTimeoutSeconds)*time.Second,
|
||||
time.Duration(repoErrorGracePeriod)*time.Second,
|
||||
metricsPort,
|
||||
@@ -164,10 +166,12 @@ func NewCommand() *cobra.Command {
|
||||
metricsAplicationLabels,
|
||||
kubectlParallelismLimit,
|
||||
persistResourceHealth,
|
||||
clusterFilter,
|
||||
clusterSharding,
|
||||
applicationNamespaces,
|
||||
&workqueueRateLimit,
|
||||
serverSideDiff,
|
||||
enableDynamicClusterDistribution,
|
||||
ignoreNormalizerOpts,
|
||||
)
|
||||
errors.CheckError(err)
|
||||
cacheutil.CollectMetrics(redisClient, appController.GetMetricsServer())
|
||||
@@ -194,6 +198,7 @@ func NewCommand() *cobra.Command {
|
||||
clientConfig = cli.AddKubectlFlagsToCmd(&command)
|
||||
command.Flags().Int64Var(&appResyncPeriod, "app-resync", int64(env.ParseDurationFromEnv("ARGOCD_RECONCILIATION_TIMEOUT", defaultAppResyncPeriod*time.Second, 0, math.MaxInt64).Seconds()), "Time period in seconds for application resync.")
|
||||
command.Flags().Int64Var(&appHardResyncPeriod, "app-hard-resync", int64(env.ParseDurationFromEnv("ARGOCD_HARD_RECONCILIATION_TIMEOUT", defaultAppHardResyncPeriod*time.Second, 0, math.MaxInt64).Seconds()), "Time period in seconds for application hard resync.")
|
||||
command.Flags().Int64Var(&appResyncJitter, "app-resync-jitter", int64(env.ParseDurationFromEnv("ARGOCD_RECONCILIATION_JITTER", 0*time.Second, 0, math.MaxInt64).Seconds()), "Maximum time period in seconds to add as a delay jitter for application resync.")
|
||||
command.Flags().Int64Var(&repoErrorGracePeriod, "repo-error-grace-period-seconds", int64(env.ParseDurationFromEnv("ARGOCD_REPO_ERROR_GRACE_PERIOD_SECONDS", defaultAppResyncPeriod*time.Second, 0, math.MaxInt64).Seconds()), "Grace period in seconds for ignoring consecutive errors while communicating with repo server.")
|
||||
command.Flags().StringVar(&repoServerAddress, "repo-server", env.StringFromEnv("ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER", common.DefaultRepoServerAddr), "Repo server address.")
|
||||
command.Flags().IntVar(&repoServerTimeoutSeconds, "repo-server-timeout-seconds", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_TIMEOUT_SECONDS", 60, 0, math.MaxInt64), "Repo server RPC call timeout seconds.")
|
||||
@@ -218,7 +223,7 @@ func NewCommand() *cobra.Command {
|
||||
command.Flags().StringVar(&shardingAlgorithm, "sharding-method", env.StringFromEnv(common.EnvControllerShardingAlgorithm, common.DefaultShardingAlgorithm), "Enables choice of sharding method. Supported sharding methods are : [legacy, round-robin] ")
|
||||
// global queue rate limit config
|
||||
command.Flags().Int64Var(&workqueueRateLimit.BucketSize, "wq-bucket-size", env.ParseInt64FromEnv("WORKQUEUE_BUCKET_SIZE", 500, 1, math.MaxInt64), "Set Workqueue Rate Limiter Bucket Size, default 500")
|
||||
command.Flags().Int64Var(&workqueueRateLimit.BucketQPS, "wq-bucket-qps", env.ParseInt64FromEnv("WORKQUEUE_BUCKET_QPS", 50, 1, math.MaxInt64), "Set Workqueue Rate Limiter Bucket QPS, default 50")
|
||||
command.Flags().Float64Var(&workqueueRateLimit.BucketQPS, "wq-bucket-qps", env.ParseFloat64FromEnv("WORKQUEUE_BUCKET_QPS", math.MaxFloat64, 1, math.MaxFloat64), "Set Workqueue Rate Limiter Bucket QPS, default set to MaxFloat64 which disables the bucket limiter")
|
||||
// individual item rate limit config
|
||||
// when WORKQUEUE_FAILURE_COOLDOWN is 0 per item rate limiting is disabled(default)
|
||||
command.Flags().DurationVar(&workqueueRateLimit.FailureCoolDown, "wq-cooldown-ns", time.Duration(env.ParseInt64FromEnv("WORKQUEUE_FAILURE_COOLDOWN_NS", 0, 0, (24*time.Hour).Nanoseconds())), "Set Workqueue Per Item Rate Limiter Cooldown duration in ns, default 0(per item rate limiter disabled)")
|
||||
@@ -227,63 +232,11 @@ func NewCommand() *cobra.Command {
|
||||
command.Flags().Float64Var(&workqueueRateLimit.BackoffFactor, "wq-backoff-factor", env.ParseFloat64FromEnv("WORKQUEUE_BACKOFF_FACTOR", 1.5, 0, math.MaxFloat64), "Set Workqueue Per Item Rate Limiter Backoff Factor, default is 1.5")
|
||||
command.Flags().BoolVar(&enableDynamicClusterDistribution, "dynamic-cluster-distribution-enabled", env.ParseBoolFromEnv(common.EnvEnableDynamicClusterDistribution, false), "Enables dynamic cluster distribution.")
|
||||
command.Flags().BoolVar(&serverSideDiff, "server-side-diff-enabled", env.ParseBoolFromEnv(common.EnvServerSideDiff, false), "Feature flag to enable ServerSide diff. Default (\"false\")")
|
||||
cacheSource = appstatecache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
|
||||
redisClient = client
|
||||
command.Flags().DurationVar(&ignoreNormalizerOpts.JQExecutionTimeout, "ignore-normalizer-jq-execution-timeout-seconds", env.ParseDurationFromEnv("ARGOCD_IGNORE_NORMALIZER_JQ_TIMEOUT", 0*time.Second, 0, math.MaxInt64), "Set ignore normalizer JQ execution timeout")
|
||||
cacheSource = appstatecache.AddCacheFlagsToCmd(&command, cacheutil.Options{
|
||||
OnClientCreated: func(client *redis.Client) {
|
||||
redisClient = client
|
||||
},
|
||||
})
|
||||
return &command
|
||||
}
|
||||
|
||||
func getClusterFilter(kubeClient *kubernetes.Clientset, settingsMgr *settings.SettingsManager, shardingAlgorithm string, enableDynamicClusterDistribution bool) sharding.ClusterFilterFunction {
|
||||
|
||||
var replicas int
|
||||
shard := env.ParseNumFromEnv(common.EnvControllerShard, -1, -math.MaxInt32, math.MaxInt32)
|
||||
|
||||
applicationControllerName := env.StringFromEnv(common.EnvAppControllerName, common.DefaultApplicationControllerName)
|
||||
appControllerDeployment, err := kubeClient.AppsV1().Deployments(settingsMgr.GetNamespace()).Get(context.Background(), applicationControllerName, metav1.GetOptions{})
|
||||
|
||||
// if the application controller deployment was not found, the Get() call returns an empty Deployment object. So, set the variable to nil explicitly
|
||||
if err != nil && kubeerrors.IsNotFound(err) {
|
||||
appControllerDeployment = nil
|
||||
}
|
||||
|
||||
if enableDynamicClusterDistribution && appControllerDeployment != nil && appControllerDeployment.Spec.Replicas != nil {
|
||||
replicas = int(*appControllerDeployment.Spec.Replicas)
|
||||
} else {
|
||||
replicas = env.ParseNumFromEnv(common.EnvControllerReplicas, 0, 0, math.MaxInt32)
|
||||
}
|
||||
|
||||
var clusterFilter func(cluster *v1alpha1.Cluster) bool
|
||||
if replicas > 1 {
|
||||
// check for shard mapping using configmap if application-controller is a deployment
|
||||
// else use existing logic to infer shard from pod name if application-controller is a statefulset
|
||||
if enableDynamicClusterDistribution && appControllerDeployment != nil {
|
||||
|
||||
var err error
|
||||
// retry 3 times if we find a conflict while updating shard mapping configMap.
|
||||
// If we still see conflicts after the retries, wait for next iteration of heartbeat process.
|
||||
for i := 0; i <= common.AppControllerHeartbeatUpdateRetryCount; i++ {
|
||||
shard, err = sharding.GetOrUpdateShardFromConfigMap(kubeClient, settingsMgr, replicas, shard)
|
||||
if !kubeerrors.IsConflict(err) {
|
||||
err = fmt.Errorf("unable to get shard due to error updating the sharding config map: %s", err)
|
||||
break
|
||||
}
|
||||
log.Warnf("conflict when getting shard from shard mapping configMap. Retrying (%d/3)", i)
|
||||
}
|
||||
errors.CheckError(err)
|
||||
} else {
|
||||
if shard < 0 {
|
||||
var err error
|
||||
shard, err = sharding.InferShard()
|
||||
errors.CheckError(err)
|
||||
}
|
||||
}
|
||||
log.Infof("Processing clusters from shard %d", shard)
|
||||
db := db.NewDB(settingsMgr.GetNamespace(), settingsMgr, kubeClient)
|
||||
log.Infof("Using filter function: %s", shardingAlgorithm)
|
||||
distributionFunction := sharding.GetDistributionFunction(db, shardingAlgorithm)
|
||||
clusterFilter = sharding.GetClusterFilter(db, distributionFunction, shard)
|
||||
} else {
|
||||
log.Info("Processing all cluster shards")
|
||||
}
|
||||
return clusterFilter
|
||||
}
|
||||
|
||||
@@ -37,13 +37,14 @@ func newAWSCommand() *cobra.Command {
|
||||
var (
|
||||
clusterName string
|
||||
roleARN string
|
||||
profile string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "aws",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
ctx := c.Context()
|
||||
|
||||
presignedURLString, err := getSignedRequestWithRetry(ctx, time.Minute, 5*time.Second, clusterName, roleARN, getSignedRequest)
|
||||
presignedURLString, err := getSignedRequestWithRetry(ctx, time.Minute, 5*time.Second, clusterName, roleARN, profile, getSignedRequest)
|
||||
errors.CheckError(err)
|
||||
token := v1Prefix + base64.RawURLEncoding.EncodeToString([]byte(presignedURLString))
|
||||
// Set token expiration to 1 minute before the presigned URL expires for some cushion
|
||||
@@ -53,16 +54,17 @@ func newAWSCommand() *cobra.Command {
|
||||
}
|
||||
command.Flags().StringVar(&clusterName, "cluster-name", "", "AWS Cluster name")
|
||||
command.Flags().StringVar(&roleARN, "role-arn", "", "AWS Role ARN")
|
||||
command.Flags().StringVar(&profile, "profile", "", "AWS Profile")
|
||||
return command
|
||||
}
|
||||
|
||||
type getSignedRequestFunc func(clusterName, roleARN string) (string, error)
|
||||
type getSignedRequestFunc func(clusterName, roleARN string, profile string) (string, error)
|
||||
|
||||
func getSignedRequestWithRetry(ctx context.Context, timeout, interval time.Duration, clusterName, roleARN string, fn getSignedRequestFunc) (string, error) {
|
||||
func getSignedRequestWithRetry(ctx context.Context, timeout, interval time.Duration, clusterName, roleARN string, profile string, fn getSignedRequestFunc) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
for {
|
||||
signed, err := fn(clusterName, roleARN)
|
||||
signed, err := fn(clusterName, roleARN, profile)
|
||||
if err == nil {
|
||||
return signed, nil
|
||||
}
|
||||
@@ -74,8 +76,10 @@ func getSignedRequestWithRetry(ctx context.Context, timeout, interval time.Durat
|
||||
}
|
||||
}
|
||||
|
||||
func getSignedRequest(clusterName, roleARN string) (string, error) {
|
||||
sess, err := session.NewSession()
|
||||
func getSignedRequest(clusterName, roleARN string, profile string) (string, error) {
|
||||
sess, err := session.NewSessionWithOptions(session.Options{
|
||||
Profile: profile,
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error creating new AWS session: %s", err)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestGetSignedRequestWithRetry(t *testing.T) {
|
||||
}
|
||||
|
||||
// when
|
||||
signed, err := getSignedRequestWithRetry(ctx, time.Second, time.Millisecond, "cluster-name", "", mock.getSignedRequestMock)
|
||||
signed, err := getSignedRequestWithRetry(ctx, time.Second, time.Millisecond, "cluster-name", "", "", mock.getSignedRequestMock)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
@@ -41,7 +41,7 @@ func TestGetSignedRequestWithRetry(t *testing.T) {
|
||||
}
|
||||
|
||||
// when
|
||||
signed, err := getSignedRequestWithRetry(ctx, time.Second, time.Millisecond, "cluster-name", "", mock.getSignedRequestMock)
|
||||
signed, err := getSignedRequestWithRetry(ctx, time.Second, time.Millisecond, "cluster-name", "", "", mock.getSignedRequestMock)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
@@ -57,7 +57,7 @@ func TestGetSignedRequestWithRetry(t *testing.T) {
|
||||
}
|
||||
|
||||
// when
|
||||
signed, err := getSignedRequestWithRetry(ctx, time.Second, time.Millisecond, "cluster-name", "", mock.getSignedRequestMock)
|
||||
signed, err := getSignedRequestWithRetry(ctx, time.Second, time.Millisecond, "cluster-name", "", "", mock.getSignedRequestMock)
|
||||
|
||||
// then
|
||||
assert.Error(t, err)
|
||||
@@ -70,7 +70,7 @@ type signedRequestMock struct {
|
||||
returnFunc func(m *signedRequestMock) (string, error)
|
||||
}
|
||||
|
||||
func (m *signedRequestMock) getSignedRequestMock(clusterName, roleARN string) (string, error) {
|
||||
func (m *signedRequestMock) getSignedRequestMock(clusterName, roleARN string, profile string) (string, error) {
|
||||
m.getSignedRequestCalls++
|
||||
return m.returnFunc(m)
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ func NewCommand() *cobra.Command {
|
||||
streamedManifestMaxTarSize string
|
||||
streamedManifestMaxExtractedSize string
|
||||
helmManifestMaxExtractedSize string
|
||||
helmRegistryMaxIndexSize string
|
||||
disableManifestMaxExtractedSize bool
|
||||
)
|
||||
var command = cobra.Command{
|
||||
@@ -110,6 +111,9 @@ func NewCommand() *cobra.Command {
|
||||
helmManifestMaxExtractedSizeQuantity, err := resource.ParseQuantity(helmManifestMaxExtractedSize)
|
||||
errors.CheckError(err)
|
||||
|
||||
helmRegistryMaxIndexSizeQuantity, err := resource.ParseQuantity(helmRegistryMaxIndexSize)
|
||||
errors.CheckError(err)
|
||||
|
||||
askPassServer := askpass.NewServer()
|
||||
metricsServer := metrics.NewMetricsServer()
|
||||
cacheutil.CollectMetrics(redisClient, metricsServer)
|
||||
@@ -125,6 +129,7 @@ func NewCommand() *cobra.Command {
|
||||
StreamedManifestMaxExtractedSize: streamedManifestMaxExtractedSizeQuantity.ToDec().Value(),
|
||||
StreamedManifestMaxTarSize: streamedManifestMaxTarSizeQuantity.ToDec().Value(),
|
||||
HelmManifestMaxExtractedSize: helmManifestMaxExtractedSizeQuantity.ToDec().Value(),
|
||||
HelmRegistryMaxIndexSize: helmRegistryMaxIndexSizeQuantity.ToDec().Value(),
|
||||
}, askPassServer)
|
||||
errors.CheckError(err)
|
||||
|
||||
@@ -208,10 +213,13 @@ func NewCommand() *cobra.Command {
|
||||
command.Flags().StringVar(&streamedManifestMaxTarSize, "streamed-manifest-max-tar-size", env.StringFromEnv("ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_TAR_SIZE", "100M"), "Maximum size of streamed manifest archives")
|
||||
command.Flags().StringVar(&streamedManifestMaxExtractedSize, "streamed-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of streamed manifest archives when extracted")
|
||||
command.Flags().StringVar(&helmManifestMaxExtractedSize, "helm-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of helm manifest archives when extracted")
|
||||
command.Flags().StringVar(&helmRegistryMaxIndexSize, "helm-registry-max-index-size", env.StringFromEnv("ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_INDEX_SIZE", "1G"), "Maximum size of registry index file")
|
||||
command.Flags().BoolVar(&disableManifestMaxExtractedSize, "disable-helm-manifest-max-extracted-size", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE", false), "Disable maximum size of helm manifest archives when extracted")
|
||||
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
|
||||
cacheSrc = reposervercache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
|
||||
redisClient = client
|
||||
cacheSrc = reposervercache.AddCacheFlagsToCmd(&command, cacheutil.Options{
|
||||
OnClientCreated: func(client *redis.Client) {
|
||||
redisClient = client
|
||||
},
|
||||
})
|
||||
return &command
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/pkg/stats"
|
||||
@@ -18,8 +19,10 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
reposervercache "github.com/argoproj/argo-cd/v2/reposerver/cache"
|
||||
"github.com/argoproj/argo-cd/v2/server"
|
||||
servercache "github.com/argoproj/argo-cd/v2/server/cache"
|
||||
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
|
||||
"github.com/argoproj/argo-cd/v2/util/cli"
|
||||
"github.com/argoproj/argo-cd/v2/util/dex"
|
||||
"github.com/argoproj/argo-cd/v2/util/env"
|
||||
@@ -61,9 +64,11 @@ func NewCommand() *cobra.Command {
|
||||
repoServerAddress string
|
||||
dexServerAddress string
|
||||
disableAuth bool
|
||||
contentTypes string
|
||||
enableGZip bool
|
||||
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
|
||||
cacheSrc func() (*servercache.Cache, error)
|
||||
repoServerCacheSrc func() (*reposervercache.Cache, error)
|
||||
frameOptions string
|
||||
contentSecurityPolicy string
|
||||
repoServerPlaintext bool
|
||||
@@ -105,6 +110,8 @@ func NewCommand() *cobra.Command {
|
||||
errors.CheckError(err)
|
||||
cache, err := cacheSrc()
|
||||
errors.CheckError(err)
|
||||
repoServerCache, err := repoServerCacheSrc()
|
||||
errors.CheckError(err)
|
||||
|
||||
kubeclientset := kubernetes.NewForConfigOrDie(config)
|
||||
|
||||
@@ -165,6 +172,11 @@ func NewCommand() *cobra.Command {
|
||||
baseHRef = rootPath
|
||||
}
|
||||
|
||||
var contentTypesList []string
|
||||
if contentTypes != "" {
|
||||
contentTypesList = strings.Split(contentTypes, ";")
|
||||
}
|
||||
|
||||
argoCDOpts := server.ArgoCDServerOpts{
|
||||
Insecure: insecure,
|
||||
ListenPort: listenPort,
|
||||
@@ -180,9 +192,11 @@ func NewCommand() *cobra.Command {
|
||||
DexServerAddr: dexServerAddress,
|
||||
DexTLSConfig: dexTlsConfig,
|
||||
DisableAuth: disableAuth,
|
||||
ContentTypes: contentTypesList,
|
||||
EnableGZip: enableGZip,
|
||||
TLSConfigCustomizer: tlsConfigCustomizer,
|
||||
Cache: cache,
|
||||
RepoServerCache: repoServerCache,
|
||||
XFrameOptions: frameOptions,
|
||||
ContentSecurityPolicy: contentSecurityPolicy,
|
||||
RedisClient: redisClient,
|
||||
@@ -234,6 +248,7 @@ func NewCommand() *cobra.Command {
|
||||
command.Flags().StringVar(&repoServerAddress, "repo-server", env.StringFromEnv("ARGOCD_SERVER_REPO_SERVER", common.DefaultRepoServerAddr), "Repo server address")
|
||||
command.Flags().StringVar(&dexServerAddress, "dex-server", env.StringFromEnv("ARGOCD_SERVER_DEX_SERVER", common.DefaultDexServerAddr), "Dex server address")
|
||||
command.Flags().BoolVar(&disableAuth, "disable-auth", env.ParseBoolFromEnv("ARGOCD_SERVER_DISABLE_AUTH", false), "Disable client authentication")
|
||||
command.Flags().StringVar(&contentTypes, "api-content-types", env.StringFromEnv("ARGOCD_API_CONTENT_TYPES", "application/json", env.StringFromEnvOpts{AllowEmpty: true}), "Semicolon separated list of allowed content types for non GET api requests. Any content type is allowed if empty.")
|
||||
command.Flags().BoolVar(&enableGZip, "enable-gzip", env.ParseBoolFromEnv("ARGOCD_SERVER_ENABLE_GZIP", true), "Enable GZIP compression")
|
||||
command.AddCommand(cli.NewVersionCmd(cliName))
|
||||
command.Flags().StringVar(&listenHost, "address", env.StringFromEnv("ARGOCD_SERVER_LISTEN_ADDRESS", common.DefaultAddressAPIServer), "Listen on given address")
|
||||
@@ -254,8 +269,11 @@ func NewCommand() *cobra.Command {
|
||||
command.Flags().StringSliceVar(&applicationNamespaces, "application-namespaces", env.StringsFromEnv("ARGOCD_APPLICATION_NAMESPACES", []string{}, ","), "List of additional namespaces where application resources can be managed in")
|
||||
command.Flags().BoolVar(&enableProxyExtension, "enable-proxy-extension", env.ParseBoolFromEnv("ARGOCD_SERVER_ENABLE_PROXY_EXTENSION", false), "Enable Proxy Extension feature")
|
||||
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(command)
|
||||
cacheSrc = servercache.AddCacheFlagsToCmd(command, func(client *redis.Client) {
|
||||
redisClient = client
|
||||
cacheSrc = servercache.AddCacheFlagsToCmd(command, cacheutil.Options{
|
||||
OnClientCreated: func(client *redis.Client) {
|
||||
redisClient = client
|
||||
},
|
||||
})
|
||||
repoServerCacheSrc = reposervercache.AddCacheFlagsToCmd(command, cacheutil.Options{FlagPrefix: "repo-server-"})
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -48,84 +48,9 @@ func NewAdminCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
c.HelpFunc()(c, args)
|
||||
},
|
||||
Example: `# List all clusters
|
||||
$ argocd admin cluster list
|
||||
|
||||
# Add a new cluster
|
||||
$ argocd admin cluster add my-cluster --name my-cluster --in-cluster-context
|
||||
|
||||
# Remove a cluster
|
||||
argocd admin cluster remove my-cluster
|
||||
|
||||
# List all projects
|
||||
$ argocd admin project list
|
||||
|
||||
# Create a new project
|
||||
$argocd admin project create my-project --src-namespace my-source-namespace --dest-namespace my-dest-namespace
|
||||
|
||||
# Update a project
|
||||
$ argocd admin project update my-project --src-namespace my-updated-source-namespace --dest-namespace my-updated-dest-namespace
|
||||
|
||||
# Delete a project
|
||||
$ argocd admin project delete my-project
|
||||
|
||||
# List all settings
|
||||
$ argocd admin settings list
|
||||
|
||||
# Get the current settings
|
||||
$ argocd admin settings get
|
||||
|
||||
# Update settings
|
||||
$ argocd admin settings update --repository.resync --value 15
|
||||
|
||||
# List all applications
|
||||
$ argocd admin app list
|
||||
|
||||
# Get application details
|
||||
$ argocd admin app get my-app
|
||||
|
||||
# Sync an application
|
||||
$ argocd admin app sync my-app
|
||||
|
||||
# Pause an application
|
||||
$ argocd admin app pause my-app
|
||||
|
||||
# Resume an application
|
||||
$ argocd admin app resume my-app
|
||||
|
||||
# List all repositories
|
||||
$ argocd admin repo list
|
||||
|
||||
# Add a repository
|
||||
$ argocd admin repo add https://github.com/argoproj/my-repo.git
|
||||
|
||||
# Remove a repository
|
||||
$ argocd admin repo remove https://github.com/argoproj/my-repo.git
|
||||
|
||||
# Import an application from a YAML file
|
||||
$ argocd admin app import -f my-app.yaml
|
||||
|
||||
# Export an application to a YAML file
|
||||
$ argocd admin app export my-app -o my-exported-app.yaml
|
||||
|
||||
# Access the Argo CD web UI
|
||||
Example: `# Access the Argo CD web UI
|
||||
$ argocd admin dashboard
|
||||
|
||||
# List notifications
|
||||
$ argocd admin notification list
|
||||
|
||||
# Get notification details
|
||||
$ argocd admin notification get my-notification
|
||||
|
||||
# Create a new notification
|
||||
$ argocd admin notification create my-notification -f notification-config.yaml
|
||||
|
||||
# Update a notification
|
||||
$ argocd admin notification update my-notification -f updated-notification-config.yaml
|
||||
|
||||
# Delete a notification
|
||||
$ argocd admin notification delete my-notification
|
||||
|
||||
# Reset the initial admin password
|
||||
$ argocd admin initial-password reset
|
||||
`,
|
||||
@@ -141,6 +66,7 @@ $ argocd admin initial-password reset
|
||||
command.AddCommand(NewDashboardCommand(clientOpts))
|
||||
command.AddCommand(NewNotificationsCommand())
|
||||
command.AddCommand(NewInitialPasswordCommand())
|
||||
command.AddCommand(NewRedisInitialPasswordCommand())
|
||||
|
||||
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")
|
||||
|
||||
@@ -24,12 +24,14 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/controller"
|
||||
"github.com/argoproj/argo-cd/v2/controller/cache"
|
||||
"github.com/argoproj/argo-cd/v2/controller/metrics"
|
||||
"github.com/argoproj/argo-cd/v2/controller/sharding"
|
||||
argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
|
||||
appinformers "github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions"
|
||||
reposerverclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
|
||||
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
|
||||
"github.com/argoproj/argo-cd/v2/util/cli"
|
||||
@@ -238,12 +240,13 @@ func diffReconcileResults(res1 reconcileResults, res2 reconcileResults) error {
|
||||
|
||||
func NewReconcileCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
clientConfig clientcmd.ClientConfig
|
||||
selector string
|
||||
repoServerAddress string
|
||||
outputFormat string
|
||||
refresh bool
|
||||
serverSideDiff bool
|
||||
clientConfig clientcmd.ClientConfig
|
||||
selector string
|
||||
repoServerAddress string
|
||||
outputFormat string
|
||||
refresh bool
|
||||
serverSideDiff bool
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
|
||||
)
|
||||
|
||||
var command = &cobra.Command{
|
||||
@@ -269,19 +272,27 @@ func NewReconcileCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
|
||||
|
||||
var result []appReconcileResult
|
||||
if refresh {
|
||||
appClientset := appclientset.NewForConfigOrDie(cfg)
|
||||
kubeClientset := kubernetes.NewForConfigOrDie(cfg)
|
||||
if repoServerAddress == "" {
|
||||
printLine("Repo server is not provided, trying to port-forward to argocd-repo-server pod.")
|
||||
overrides := clientcmd.ConfigOverrides{}
|
||||
repoServerPodLabelSelector := common.LabelKeyAppName + "=" + clientOpts.RepoServerName
|
||||
repoServerName := clientOpts.RepoServerName
|
||||
repoServerServiceLabelSelector := common.LabelKeyComponentRepoServer + "=" + common.LabelValueComponentRepoServer
|
||||
repoServerServices, err := kubeClientset.CoreV1().Services(namespace).List(context.Background(), v1.ListOptions{LabelSelector: repoServerServiceLabelSelector})
|
||||
errors.CheckError(err)
|
||||
if len(repoServerServices.Items) > 0 {
|
||||
if repoServerServicelabel, ok := repoServerServices.Items[0].Labels[common.LabelKeyAppName]; ok && repoServerServicelabel != "" {
|
||||
repoServerName = repoServerServicelabel
|
||||
}
|
||||
}
|
||||
repoServerPodLabelSelector := common.LabelKeyAppName + "=" + repoServerName
|
||||
repoServerPort, err := kubeutil.PortForward(8081, namespace, &overrides, repoServerPodLabelSelector)
|
||||
errors.CheckError(err)
|
||||
repoServerAddress = fmt.Sprintf("localhost:%d", repoServerPort)
|
||||
}
|
||||
repoServerClient := reposerverclient.NewRepoServerClientset(repoServerAddress, 60, reposerverclient.TLSConfiguration{DisableTLS: false, StrictValidation: false})
|
||||
|
||||
appClientset := appclientset.NewForConfigOrDie(cfg)
|
||||
kubeClientset := kubernetes.NewForConfigOrDie(cfg)
|
||||
result, err = reconcileApplications(ctx, kubeClientset, appClientset, namespace, repoServerClient, selector, newLiveStateCache, serverSideDiff)
|
||||
result, err = reconcileApplications(ctx, kubeClientset, appClientset, namespace, repoServerClient, selector, newLiveStateCache, serverSideDiff, ignoreNormalizerOpts)
|
||||
errors.CheckError(err)
|
||||
} else {
|
||||
appClientset := appclientset.NewForConfigOrDie(cfg)
|
||||
@@ -297,7 +308,7 @@ func NewReconcileCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
|
||||
command.Flags().StringVar(&outputFormat, "o", "yaml", "Output format (yaml|json)")
|
||||
command.Flags().BoolVar(&refresh, "refresh", false, "If set to true then recalculates apps reconciliation")
|
||||
command.Flags().BoolVar(&serverSideDiff, "server-side-diff", false, "If set to \"true\" will use server-side diff while comparing resources. Default (\"false\")")
|
||||
|
||||
command.Flags().DurationVar(&ignoreNormalizerOpts.JQExecutionTimeout, "ignore-normalizer-jq-execution-timeout", normalizers.DefaultJQExecutionTimeout, "Set ignore normalizer JQ execution timeout")
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -347,6 +358,7 @@ func reconcileApplications(
|
||||
selector string,
|
||||
createLiveStateCache func(argoDB db.ArgoDB, appInformer kubecache.SharedIndexInformer, settingsMgr *settings.SettingsManager, server *metrics.MetricsServer) cache.LiveStateCache,
|
||||
serverSideDiff bool,
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts,
|
||||
) ([]appReconcileResult, error) {
|
||||
settingsMgr := settings.NewSettingsManager(ctx, kubeClientset, namespace)
|
||||
argoDB := db.NewDB(namespace, settingsMgr, kubeClientset)
|
||||
@@ -387,7 +399,7 @@ func reconcileApplications(
|
||||
)
|
||||
|
||||
appStateManager := controller.NewAppStateManager(
|
||||
argoDB, appClientset, repoServerClient, namespace, kubeutil.NewKubectl(), settingsMgr, stateCache, projInformer, server, cache, time.Second, argo.NewResourceTracking(), false, 0, serverSideDiff)
|
||||
argoDB, appClientset, repoServerClient, namespace, kubeutil.NewKubectl(), settingsMgr, stateCache, projInformer, server, cache, time.Second, argo.NewResourceTracking(), false, 0, serverSideDiff, ignoreNormalizerOpts)
|
||||
|
||||
appsList, err := appClientset.ArgoprojV1alpha1().Applications(namespace).List(ctx, v1.ListOptions{LabelSelector: selector})
|
||||
if err != nil {
|
||||
@@ -437,5 +449,5 @@ func reconcileApplications(
|
||||
}
|
||||
|
||||
func newLiveStateCache(argoDB db.ArgoDB, appInformer kubecache.SharedIndexInformer, settingsMgr *settings.SettingsManager, server *metrics.MetricsServer) cache.LiveStateCache {
|
||||
return cache.NewLiveStateCache(argoDB, appInformer, settingsMgr, kubeutil.NewKubectl(), server, func(managedByApp map[string]bool, ref apiv1.ObjectReference) {}, nil, argo.NewResourceTracking())
|
||||
return cache.NewLiveStateCache(argoDB, appInformer, settingsMgr, kubeutil.NewKubectl(), server, func(managedByApp map[string]bool, ref apiv1.ObjectReference) {}, &sharding.ClusterSharding{}, argo.NewResourceTracking())
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
argocdclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
|
||||
"github.com/argoproj/argo-cd/v2/test"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
"github.com/argoproj/argo-cd/v2/util/db"
|
||||
"github.com/argoproj/argo-cd/v2/util/settings"
|
||||
)
|
||||
@@ -114,6 +115,7 @@ func TestGetReconcileResults_Refresh(t *testing.T) {
|
||||
return &liveStateCache
|
||||
},
|
||||
false,
|
||||
normalizers.IgnoreNormalizerOpts{},
|
||||
)
|
||||
|
||||
if !assert.NoError(t, err) {
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/controller/sharding"
|
||||
argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
|
||||
argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
|
||||
@@ -71,14 +71,14 @@ argocd admin cluster namespaces my-cluster `,
|
||||
}
|
||||
|
||||
type ClusterWithInfo struct {
|
||||
argoappv1.Cluster
|
||||
v1alpha1.Cluster
|
||||
// Shard holds controller shard number that handles the cluster
|
||||
Shard int
|
||||
// Namespaces holds list of namespaces managed by Argo CD in the cluster
|
||||
Namespaces []string
|
||||
}
|
||||
|
||||
func loadClusters(ctx context.Context, kubeClient *kubernetes.Clientset, appClient *versioned.Clientset, replicas int, namespace string, portForwardRedis bool, cacheSrc func() (*appstatecache.Cache, error), shard int, redisName string, redisHaProxyName string, redisCompressionStr string) ([]ClusterWithInfo, error) {
|
||||
func loadClusters(ctx context.Context, kubeClient *kubernetes.Clientset, appClient *versioned.Clientset, replicas int, shardingAlgorithm string, namespace string, portForwardRedis bool, cacheSrc func() (*appstatecache.Cache, error), shard int, redisName string, redisHaProxyName string, redisCompressionStr string) ([]ClusterWithInfo, error) {
|
||||
settingsMgr := settings.NewSettingsManager(ctx, kubeClient, namespace)
|
||||
|
||||
argoDB := db.NewDB(namespace, settingsMgr, kubeClient)
|
||||
@@ -86,6 +86,14 @@ func loadClusters(ctx context.Context, kubeClient *kubernetes.Clientset, appClie
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appItems, err := appClient.ArgoprojV1alpha1().Applications(namespace).List(ctx, v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clusterShardingCache := sharding.NewClusterSharding(argoDB, shard, replicas, shardingAlgorithm)
|
||||
clusterShardingCache.Init(clustersList, appItems)
|
||||
clusterShards := clusterShardingCache.GetDistribution()
|
||||
|
||||
var cache *appstatecache.Cache
|
||||
if portForwardRedis {
|
||||
overrides := clientcmd.ConfigOverrides{}
|
||||
@@ -109,10 +117,6 @@ func loadClusters(ctx context.Context, kubeClient *kubernetes.Clientset, appClie
|
||||
}
|
||||
}
|
||||
|
||||
appItems, err := appClient.ArgoprojV1alpha1().Applications(namespace).List(ctx, v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apps := appItems.Items
|
||||
for i, app := range apps {
|
||||
err := argo.ValidateDestination(ctx, &app.Spec.Destination, argoDB)
|
||||
@@ -122,6 +126,7 @@ func loadClusters(ctx context.Context, kubeClient *kubernetes.Clientset, appClie
|
||||
apps[i] = app
|
||||
}
|
||||
clusters := make([]ClusterWithInfo, len(clustersList.Items))
|
||||
|
||||
batchSize := 10
|
||||
batchesCount := int(math.Ceil(float64(len(clusters)) / float64(batchSize)))
|
||||
for batchNum := 0; batchNum < batchesCount; batchNum++ {
|
||||
@@ -135,12 +140,10 @@ func loadClusters(ctx context.Context, kubeClient *kubernetes.Clientset, appClie
|
||||
clusterShard := 0
|
||||
cluster := batch[i]
|
||||
if replicas > 0 {
|
||||
distributionFunction := sharding.GetDistributionFunction(argoDB, common.DefaultShardingAlgorithm)
|
||||
distributionFunction(&cluster)
|
||||
clusterShard = clusterShards[cluster.Server]
|
||||
cluster.Shard = pointer.Int64(int64(clusterShard))
|
||||
log.Infof("Cluster with uid: %s will be processed by shard %d", cluster.ID, clusterShard)
|
||||
}
|
||||
|
||||
if shard != -1 && clusterShard != shard {
|
||||
return nil
|
||||
}
|
||||
@@ -176,6 +179,7 @@ func NewClusterShardsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
|
||||
var (
|
||||
shard int
|
||||
replicas int
|
||||
shardingAlgorithm string
|
||||
clientConfig clientcmd.ClientConfig
|
||||
cacheSrc func() (*appstatecache.Cache, error)
|
||||
portForwardRedis bool
|
||||
@@ -183,7 +187,7 @@ func NewClusterShardsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
|
||||
)
|
||||
var command = cobra.Command{
|
||||
Use: "shards",
|
||||
Short: "Print information about each controller shard and portion of Kubernetes resources it is responsible for.",
|
||||
Short: "Print information about each controller shard and the estimated portion of Kubernetes resources it is responsible for.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := cmd.Context()
|
||||
|
||||
@@ -203,8 +207,7 @@ func NewClusterShardsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
|
||||
if replicas == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
clusters, err := loadClusters(ctx, kubeClient, appClient, replicas, namespace, portForwardRedis, cacheSrc, shard, clientOpts.RedisName, clientOpts.RedisHaProxyName, redisCompressionStr)
|
||||
clusters, err := loadClusters(ctx, kubeClient, appClient, replicas, shardingAlgorithm, namespace, portForwardRedis, cacheSrc, shard, clientOpts.RedisName, clientOpts.RedisHaProxyName, redisCompressionStr)
|
||||
errors.CheckError(err)
|
||||
if len(clusters) == 0 {
|
||||
return
|
||||
@@ -216,7 +219,9 @@ func NewClusterShardsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
|
||||
clientConfig = cli.AddKubectlFlagsToCmd(&command)
|
||||
command.Flags().IntVar(&shard, "shard", -1, "Cluster shard filter")
|
||||
command.Flags().IntVar(&replicas, "replicas", 0, "Application controller replicas count. Inferred from number of running controller pods if not specified")
|
||||
command.Flags().StringVar(&shardingAlgorithm, "sharding-method", common.DefaultShardingAlgorithm, "Sharding method. Defaults: legacy. Supported sharding methods are : [legacy, round-robin] ")
|
||||
command.Flags().BoolVar(&portForwardRedis, "port-forward-redis", true, "Automatically port-forward ha proxy redis from current namespace?")
|
||||
|
||||
cacheSrc = appstatecache.AddCacheFlagsToCmd(&command)
|
||||
|
||||
// parse all added flags so far to get the redis-compression flag that was added by AddCacheFlagsToCmd() above
|
||||
@@ -461,6 +466,7 @@ func NewClusterStatsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comma
|
||||
var (
|
||||
shard int
|
||||
replicas int
|
||||
shardingAlgorithm string
|
||||
clientConfig clientcmd.ClientConfig
|
||||
cacheSrc func() (*appstatecache.Cache, error)
|
||||
portForwardRedis bool
|
||||
@@ -494,7 +500,7 @@ argocd admin cluster stats target-cluster`,
|
||||
replicas, err = getControllerReplicas(ctx, kubeClient, namespace, clientOpts.AppControllerName)
|
||||
errors.CheckError(err)
|
||||
}
|
||||
clusters, err := loadClusters(ctx, kubeClient, appClient, replicas, namespace, portForwardRedis, cacheSrc, shard, clientOpts.RedisName, clientOpts.RedisHaProxyName, redisCompressionStr)
|
||||
clusters, err := loadClusters(ctx, kubeClient, appClient, replicas, shardingAlgorithm, namespace, portForwardRedis, cacheSrc, shard, clientOpts.RedisName, clientOpts.RedisHaProxyName, redisCompressionStr)
|
||||
errors.CheckError(err)
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
@@ -508,6 +514,7 @@ argocd admin cluster stats target-cluster`,
|
||||
clientConfig = cli.AddKubectlFlagsToCmd(&command)
|
||||
command.Flags().IntVar(&shard, "shard", -1, "Cluster shard filter")
|
||||
command.Flags().IntVar(&replicas, "replicas", 0, "Application controller replicas count. Inferred from number of running controller pods if not specified")
|
||||
command.Flags().StringVar(&shardingAlgorithm, "sharding-method", common.DefaultShardingAlgorithm, "Sharding method. Defaults: legacy. Supported sharding methods are : [legacy, round-robin] ")
|
||||
command.Flags().BoolVar(&portForwardRedis, "port-forward-redis", true, "Automatically port-forward ha proxy redis from current namespace?")
|
||||
cacheSrc = appstatecache.AddCacheFlagsToCmd(&command)
|
||||
|
||||
@@ -610,15 +617,16 @@ func NewGenClusterConfigCommand(pathOpts *clientcmd.PathOptions) *cobra.Command
|
||||
errors.CheckError(err)
|
||||
kubeClientset := fake.NewSimpleClientset()
|
||||
|
||||
var awsAuthConf *argoappv1.AWSAuthConfig
|
||||
var execProviderConf *argoappv1.ExecProviderConfig
|
||||
var awsAuthConf *v1alpha1.AWSAuthConfig
|
||||
var execProviderConf *v1alpha1.ExecProviderConfig
|
||||
if clusterOpts.AwsClusterName != "" {
|
||||
awsAuthConf = &argoappv1.AWSAuthConfig{
|
||||
awsAuthConf = &v1alpha1.AWSAuthConfig{
|
||||
ClusterName: clusterOpts.AwsClusterName,
|
||||
RoleARN: clusterOpts.AwsRoleArn,
|
||||
Profile: clusterOpts.AwsProfile,
|
||||
}
|
||||
} else if clusterOpts.ExecProviderCommand != "" {
|
||||
execProviderConf = &argoappv1.ExecProviderConfig{
|
||||
execProviderConf = &v1alpha1.ExecProviderConfig{
|
||||
Command: clusterOpts.ExecProviderCommand,
|
||||
Args: clusterOpts.ExecProviderArgs,
|
||||
Env: clusterOpts.ExecProviderEnv,
|
||||
@@ -642,7 +650,7 @@ func NewGenClusterConfigCommand(pathOpts *clientcmd.PathOptions) *cobra.Command
|
||||
|
||||
clst := cmdutil.NewCluster(contextName, clusterOpts.Namespaces, clusterOpts.ClusterResources, conf, bearerToken, awsAuthConf, execProviderConf, labelsMap, annotationsMap)
|
||||
if clusterOpts.InClusterEndpoint() {
|
||||
clst.Server = argoappv1.KubernetesInternalAPIServerAddr
|
||||
clst.Server = v1alpha1.KubernetesInternalAPIServerAddr
|
||||
}
|
||||
if clusterOpts.ClusterEndpoint == string(cmdutil.KubePublicEndpoint) {
|
||||
// Ignore `kube-public` cluster endpoints, since this command is intended to run without invoking any network connections.
|
||||
|
||||
98
cmd/argocd/commands/admin/redis_initial_password.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/cli"
|
||||
apierr "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/util/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
const defaulRedisInitialPasswordSecretName = "argocd-redis"
|
||||
const defaultResisInitialPasswordKey = "auth"
|
||||
|
||||
func generateRandomPassword() (string, error) {
|
||||
const initialPasswordLength = 16
|
||||
const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
|
||||
randBytes := make([]byte, initialPasswordLength)
|
||||
for i := 0; i < initialPasswordLength; i++ {
|
||||
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
randBytes[i] = letters[num.Int64()]
|
||||
}
|
||||
initialPassword := string(randBytes)
|
||||
return initialPassword, nil
|
||||
}
|
||||
|
||||
// NewRedisInitialPasswordCommand defines a new command to ensure Argo CD Redis password secret exists.
|
||||
func NewRedisInitialPasswordCommand() *cobra.Command {
|
||||
var (
|
||||
clientConfig clientcmd.ClientConfig
|
||||
)
|
||||
var command = cobra.Command{
|
||||
Use: "redis-initial-password",
|
||||
Short: "Ensure the Redis password exists, creating a new one if necessary.",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
namespace, _, err := clientConfig.Namespace()
|
||||
errors.CheckError(err)
|
||||
|
||||
redisInitialPasswordSecretName := defaulRedisInitialPasswordSecretName
|
||||
redisInitialPasswordKey := defaultResisInitialPasswordKey
|
||||
fmt.Printf("Checking for initial Redis password in secret %s/%s at key %s. \n", namespace, redisInitialPasswordSecretName, redisInitialPasswordKey)
|
||||
|
||||
config, err := clientConfig.ClientConfig()
|
||||
errors.CheckError(err)
|
||||
errors.CheckError(v1alpha1.SetK8SConfigDefaults(config))
|
||||
|
||||
kubeClientset := kubernetes.NewForConfigOrDie(config)
|
||||
|
||||
randomPassword, err := generateRandomPassword()
|
||||
errors.CheckError(err)
|
||||
|
||||
data := map[string][]byte{
|
||||
redisInitialPasswordKey: []byte(randomPassword),
|
||||
}
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: redisInitialPasswordSecretName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: data,
|
||||
Type: corev1.SecretTypeOpaque,
|
||||
}
|
||||
_, err = kubeClientset.CoreV1().Secrets(namespace).Create(context.Background(), secret, metav1.CreateOptions{})
|
||||
if err != nil && !apierr.IsAlreadyExists(err) {
|
||||
errors.CheckError(err)
|
||||
}
|
||||
|
||||
fmt.Println("Argo CD Redis secret state confirmed: secret name argocd-redis.")
|
||||
secret, err = kubeClientset.CoreV1().Secrets(namespace).Get(context.Background(), redisInitialPasswordSecretName, v1.GetOptions{})
|
||||
errors.CheckError(err)
|
||||
|
||||
if _, ok := secret.Data[redisInitialPasswordKey]; ok {
|
||||
fmt.Println("Password secret is configured properly.")
|
||||
} else {
|
||||
err := fmt.Errorf("key %s doesn't exist in secret %s. \n", redisInitialPasswordKey, redisInitialPasswordSecretName)
|
||||
errors.CheckError(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
clientConfig = cli.AddKubectlFlagsToCmd(&command)
|
||||
|
||||
return &command
|
||||
}
|
||||
@@ -428,7 +428,7 @@ argocd admin settings resource-overrides ignore-differences ./deploy.yaml --argo
|
||||
// configurations. This requires access to live resources which is not the
|
||||
// purpose of this command. This will just apply jsonPointers and
|
||||
// jqPathExpressions configurations.
|
||||
normalizer, err := normalizers.NewIgnoreNormalizer(nil, overrides)
|
||||
normalizer, err := normalizers.NewIgnoreNormalizer(nil, overrides, normalizers.IgnoreNormalizerOpts{})
|
||||
errors.CheckError(err)
|
||||
|
||||
normalizedRes := res.DeepCopy()
|
||||
@@ -453,6 +453,9 @@ argocd admin settings resource-overrides ignore-differences ./deploy.yaml --argo
|
||||
}
|
||||
|
||||
func NewResourceIgnoreResourceUpdatesCommand(cmdCtx commandContext) *cobra.Command {
|
||||
var (
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "ignore-resource-updates RESOURCE_YAML_PATH",
|
||||
Short: "Renders fields excluded from resource updates",
|
||||
@@ -474,7 +477,7 @@ argocd admin settings resource-overrides ignore-resource-updates ./deploy.yaml -
|
||||
return
|
||||
}
|
||||
|
||||
normalizer, err := normalizers.NewIgnoreNormalizer(nil, overrides)
|
||||
normalizer, err := normalizers.NewIgnoreNormalizer(nil, overrides, ignoreNormalizerOpts)
|
||||
errors.CheckError(err)
|
||||
|
||||
normalizedRes := res.DeepCopy()
|
||||
@@ -495,6 +498,7 @@ argocd admin settings resource-overrides ignore-resource-updates ./deploy.yaml -
|
||||
})
|
||||
},
|
||||
}
|
||||
command.Flags().DurationVar(&ignoreNormalizerOpts.JQExecutionTimeout, "ignore-normalizer-jq-execution-timeout", normalizers.DefaultJQExecutionTimeout, "Set ignore normalizer JQ execution timeout")
|
||||
return command
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,43 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
|
||||
accountpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/account"
|
||||
applicationpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
|
||||
applicationsetpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/applicationset"
|
||||
certificatepkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/certificate"
|
||||
clusterpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster"
|
||||
gpgkeypkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/gpgkey"
|
||||
notificationpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/notification"
|
||||
projectpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/project"
|
||||
repocredspkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/repocreds"
|
||||
repositorypkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/repository"
|
||||
sessionpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/session"
|
||||
settingspkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/settings"
|
||||
versionpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/version"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/health"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/oauth2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
k8swatch "k8s.io/apimachinery/pkg/watch"
|
||||
)
|
||||
|
||||
func Test_getInfos(t *testing.T) {
|
||||
@@ -402,8 +422,8 @@ func TestFormatSyncPolicy(t *testing.T) {
|
||||
|
||||
policy := formatSyncPolicy(app)
|
||||
|
||||
if policy != "<none>" {
|
||||
t.Fatalf("Incorrect policy %q, should be <none>", policy)
|
||||
if policy != "Manual" {
|
||||
t.Fatalf("Incorrect policy %q, should be Manual", policy)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -537,18 +557,21 @@ func TestPrintApplicationHistoryTable(t *testing.T) {
|
||||
ID: 1,
|
||||
Source: v1alpha1.ApplicationSource{
|
||||
TargetRevision: "1",
|
||||
RepoURL: "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Source: v1alpha1.ApplicationSource{
|
||||
TargetRevision: "2",
|
||||
RepoURL: "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
Source: v1alpha1.ApplicationSource{
|
||||
TargetRevision: "3",
|
||||
RepoURL: "test",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -558,7 +581,86 @@ func TestPrintApplicationHistoryTable(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
|
||||
expectation := "ID DATE REVISION\n1 0001-01-01 00:00:00 +0000 UTC 1\n2 0001-01-01 00:00:00 +0000 UTC 2\n3 0001-01-01 00:00:00 +0000 UTC 3\n"
|
||||
expectation := "SOURCE test\nID DATE REVISION\n1 0001-01-01 00:00:00 +0000 UTC 1\n2 0001-01-01 00:00:00 +0000 UTC 2\n3 0001-01-01 00:00:00 +0000 UTC 3\n"
|
||||
|
||||
if output != expectation {
|
||||
t.Fatalf("Incorrect print operation output %q, should be %q", output, expectation)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintApplicationHistoryTableWithMultipleSources(t *testing.T) {
|
||||
histories := []v1alpha1.RevisionHistory{
|
||||
{
|
||||
ID: 0,
|
||||
Source: v1alpha1.ApplicationSource{
|
||||
TargetRevision: "0",
|
||||
RepoURL: "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 1,
|
||||
Revisions: []string{
|
||||
"1a",
|
||||
"1b",
|
||||
},
|
||||
//added Source just for testing the fuction
|
||||
Source: v1alpha1.ApplicationSource{
|
||||
TargetRevision: "-1",
|
||||
RepoURL: "ignore",
|
||||
},
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
v1alpha1.ApplicationSource{
|
||||
RepoURL: "test-1",
|
||||
TargetRevision: "1a",
|
||||
},
|
||||
v1alpha1.ApplicationSource{
|
||||
RepoURL: "test-2",
|
||||
TargetRevision: "1b",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Revisions: []string{
|
||||
"2a",
|
||||
"2b",
|
||||
},
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
v1alpha1.ApplicationSource{
|
||||
RepoURL: "test-1",
|
||||
TargetRevision: "2a",
|
||||
},
|
||||
v1alpha1.ApplicationSource{
|
||||
RepoURL: "test-2",
|
||||
TargetRevision: "2b",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
Revisions: []string{
|
||||
"3a",
|
||||
"3b",
|
||||
},
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
v1alpha1.ApplicationSource{
|
||||
RepoURL: "test-1",
|
||||
TargetRevision: "3a",
|
||||
},
|
||||
v1alpha1.ApplicationSource{
|
||||
RepoURL: "test-2",
|
||||
TargetRevision: "3b",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
output, _ := captureOutput(func() error {
|
||||
printApplicationHistoryTable(histories)
|
||||
return nil
|
||||
})
|
||||
|
||||
expectation := "SOURCE test\nID DATE REVISION\n0 0001-01-01 00:00:00 +0000 UTC 0\n\nSOURCE test-1\nID DATE REVISION\n1 0001-01-01 00:00:00 +0000 UTC 1a\n2 0001-01-01 00:00:00 +0000 UTC 2a\n3 0001-01-01 00:00:00 +0000 UTC 3a\n\nSOURCE test-2\nID DATE REVISION\n1 0001-01-01 00:00:00 +0000 UTC 1b\n2 0001-01-01 00:00:00 +0000 UTC 2b\n3 0001-01-01 00:00:00 +0000 UTC 3b\n"
|
||||
|
||||
if output != expectation {
|
||||
t.Fatalf("Incorrect print operation output %q, should be %q", output, expectation)
|
||||
@@ -639,11 +741,110 @@ Project: default
|
||||
Server: local
|
||||
Namespace: argocd
|
||||
URL: url
|
||||
Repo: test
|
||||
Target: master
|
||||
Path: /test
|
||||
Helm Values: path1,path2
|
||||
Name Prefix: prefix
|
||||
Source:
|
||||
- Repo: test
|
||||
Target: master
|
||||
Path: /test
|
||||
Helm Values: path1,path2
|
||||
Name Prefix: prefix
|
||||
SyncWindow: Sync Denied
|
||||
Assigned Windows: allow:0 0 * * *:24h,deny:0 0 * * *:24h,allow:0 0 * * *:24h
|
||||
Sync Policy: Automated (Prune)
|
||||
Sync Status: OutOfSync from master
|
||||
Health Status: Progressing (health-message)
|
||||
`
|
||||
assert.Equalf(t, expectation, output, "Incorrect print app summary output %q, should be %q", output, expectation)
|
||||
}
|
||||
|
||||
func TestPrintAppSummaryTable_MultipleSources(t *testing.T) {
|
||||
output, _ := captureOutput(func() error {
|
||||
app := &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
SyncPolicy: &v1alpha1.SyncPolicy{
|
||||
Automated: &v1alpha1.SyncPolicyAutomated{
|
||||
Prune: true,
|
||||
},
|
||||
},
|
||||
Project: "default",
|
||||
Destination: v1alpha1.ApplicationDestination{Server: "local", Namespace: "argocd"},
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
{
|
||||
RepoURL: "test",
|
||||
TargetRevision: "master",
|
||||
Path: "/test",
|
||||
Helm: &v1alpha1.ApplicationSourceHelm{
|
||||
ValueFiles: []string{"path1", "path2"},
|
||||
},
|
||||
Kustomize: &v1alpha1.ApplicationSourceKustomize{NamePrefix: "prefix"},
|
||||
}, {
|
||||
RepoURL: "test2",
|
||||
TargetRevision: "master2",
|
||||
Path: "/test2",
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationStatus{
|
||||
Sync: v1alpha1.SyncStatus{
|
||||
Status: v1alpha1.SyncStatusCodeOutOfSync,
|
||||
},
|
||||
Health: v1alpha1.HealthStatus{
|
||||
Status: health.HealthStatusProgressing,
|
||||
Message: "health-message",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
windows := &v1alpha1.SyncWindows{
|
||||
{
|
||||
Kind: "allow",
|
||||
Schedule: "0 0 * * *",
|
||||
Duration: "24h",
|
||||
Applications: []string{
|
||||
"*-prod",
|
||||
},
|
||||
ManualSync: true,
|
||||
},
|
||||
{
|
||||
Kind: "deny",
|
||||
Schedule: "0 0 * * *",
|
||||
Duration: "24h",
|
||||
Namespaces: []string{
|
||||
"default",
|
||||
},
|
||||
},
|
||||
{
|
||||
Kind: "allow",
|
||||
Schedule: "0 0 * * *",
|
||||
Duration: "24h",
|
||||
Clusters: []string{
|
||||
"in-cluster",
|
||||
"cluster1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
printAppSummaryTable(app, "url", windows)
|
||||
return nil
|
||||
})
|
||||
|
||||
expectation := `Name: argocd/test
|
||||
Project: default
|
||||
Server: local
|
||||
Namespace: argocd
|
||||
URL: url
|
||||
Sources:
|
||||
- Repo: test
|
||||
Target: master
|
||||
Path: /test
|
||||
Helm Values: path1,path2
|
||||
Name Prefix: prefix
|
||||
- Repo: test2
|
||||
Target: master2
|
||||
Path: /test2
|
||||
SyncWindow: Sync Denied
|
||||
Assigned Windows: allow:0 0 * * *:24h,deny:0 0 * * *:24h,allow:0 0 * * *:24h
|
||||
Sync Policy: Automated (Prune)
|
||||
@@ -806,6 +1007,14 @@ func TestTargetObjects_invalid(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestCheckForDeleteEvent(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
fakeClient := new(fakeAcdClient)
|
||||
|
||||
checkForDeleteEvent(ctx, fakeClient, "testApp")
|
||||
}
|
||||
|
||||
func TestPrintApplicationNames(t *testing.T) {
|
||||
output, _ := captureOutput(func() error {
|
||||
app := &v1alpha1.Application{
|
||||
@@ -1301,7 +1510,7 @@ func TestPrintApplicationTableNotWide(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
expectation := "NAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS\napp-name http://localhost:8080 default prj OutOfSync Healthy <none> <none>\napp-name http://localhost:8080 default prj OutOfSync Healthy <none> <none>\n"
|
||||
expectation := "NAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS\napp-name http://localhost:8080 default prj OutOfSync Healthy Manual <none>\napp-name http://localhost:8080 default prj OutOfSync Healthy Manual <none>\n"
|
||||
assert.Equal(t, output, expectation)
|
||||
}
|
||||
|
||||
@@ -1337,7 +1546,7 @@ func TestPrintApplicationTableWide(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
expectation := "NAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS REPO PATH TARGET\napp-name http://localhost:8080 default prj OutOfSync Healthy <none> <none> https://github.com/argoproj/argocd-example-apps guestbook 123\napp-name http://localhost:8080 default prj OutOfSync Healthy <none> <none> https://github.com/argoproj/argocd-example-apps guestbook 123\n"
|
||||
expectation := "NAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS REPO PATH TARGET\napp-name http://localhost:8080 default prj OutOfSync Healthy Manual <none> https://github.com/argoproj/argocd-example-apps guestbook 123\napp-name http://localhost:8080 default prj OutOfSync Healthy Manual <none> https://github.com/argoproj/argocd-example-apps guestbook 123\n"
|
||||
assert.Equal(t, output, expectation)
|
||||
}
|
||||
|
||||
@@ -1599,3 +1808,104 @@ func testApp(name, project string, labels map[string]string, annotations map[str
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type fakeAcdClient struct{}
|
||||
|
||||
func (c *fakeAcdClient) ClientOptions() argocdclient.ClientOptions {
|
||||
return argocdclient.ClientOptions{}
|
||||
}
|
||||
func (c *fakeAcdClient) HTTPClient() (*http.Client, error) { return nil, nil }
|
||||
func (c *fakeAcdClient) OIDCConfig(context.Context, *settingspkg.Settings) (*oauth2.Config, *oidc.Provider, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewRepoClient() (io.Closer, repositorypkg.RepositoryServiceClient, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewRepoClientOrDie() (io.Closer, repositorypkg.RepositoryServiceClient) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewRepoCredsClient() (io.Closer, repocredspkg.RepoCredsServiceClient, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewRepoCredsClientOrDie() (io.Closer, repocredspkg.RepoCredsServiceClient) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewCertClient() (io.Closer, certificatepkg.CertificateServiceClient, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewCertClientOrDie() (io.Closer, certificatepkg.CertificateServiceClient) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewClusterClient() (io.Closer, clusterpkg.ClusterServiceClient, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewClusterClientOrDie() (io.Closer, clusterpkg.ClusterServiceClient) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewGPGKeyClient() (io.Closer, gpgkeypkg.GPGKeyServiceClient, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewGPGKeyClientOrDie() (io.Closer, gpgkeypkg.GPGKeyServiceClient) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewApplicationClient() (io.Closer, applicationpkg.ApplicationServiceClient, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewApplicationSetClient() (io.Closer, applicationsetpkg.ApplicationSetServiceClient, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewApplicationClientOrDie() (io.Closer, applicationpkg.ApplicationServiceClient) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewApplicationSetClientOrDie() (io.Closer, applicationsetpkg.ApplicationSetServiceClient) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewNotificationClient() (io.Closer, notificationpkg.NotificationServiceClient, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewNotificationClientOrDie() (io.Closer, notificationpkg.NotificationServiceClient) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewSessionClient() (io.Closer, sessionpkg.SessionServiceClient, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewSessionClientOrDie() (io.Closer, sessionpkg.SessionServiceClient) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewSettingsClient() (io.Closer, settingspkg.SettingsServiceClient, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewSettingsClientOrDie() (io.Closer, settingspkg.SettingsServiceClient) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewVersionClient() (io.Closer, versionpkg.VersionServiceClient, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewVersionClientOrDie() (io.Closer, versionpkg.VersionServiceClient) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewProjectClient() (io.Closer, projectpkg.ProjectServiceClient, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewProjectClientOrDie() (io.Closer, projectpkg.ProjectServiceClient) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewAccountClient() (io.Closer, accountpkg.AccountServiceClient, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) NewAccountClientOrDie() (io.Closer, accountpkg.AccountServiceClient) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *fakeAcdClient) WatchApplicationWithRetry(ctx context.Context, appName string, revision string) chan *v1alpha1.ApplicationWatchEvent {
|
||||
appEventsCh := make(chan *v1alpha1.ApplicationWatchEvent)
|
||||
|
||||
go func() {
|
||||
modifiedEvent := new(v1alpha1.ApplicationWatchEvent)
|
||||
modifiedEvent.Type = k8swatch.Modified
|
||||
appEventsCh <- modifiedEvent
|
||||
deletedEvent := new(v1alpha1.ApplicationWatchEvent)
|
||||
deletedEvent.Type = k8swatch.Deleted
|
||||
appEventsCh <- deletedEvent
|
||||
}()
|
||||
return appEventsCh
|
||||
}
|
||||
|
||||
@@ -350,9 +350,11 @@ func printAppSetSummaryTable(appSet *arogappsetv1.ApplicationSet) {
|
||||
fmt.Printf(printOpFmtStr, "Project:", appSet.Spec.Template.Spec.GetProject())
|
||||
fmt.Printf(printOpFmtStr, "Server:", getServerForAppSet(appSet))
|
||||
fmt.Printf(printOpFmtStr, "Namespace:", appSet.Spec.Template.Spec.Destination.Namespace)
|
||||
fmt.Printf(printOpFmtStr, "Repo:", source.RepoURL)
|
||||
fmt.Printf(printOpFmtStr, "Target:", source.TargetRevision)
|
||||
fmt.Printf(printOpFmtStr, "Path:", source.Path)
|
||||
if !appSet.Spec.Template.Spec.HasMultipleSources() {
|
||||
fmt.Println("Source:")
|
||||
} else {
|
||||
fmt.Println("Sources:")
|
||||
}
|
||||
printAppSourceDetails(&source)
|
||||
|
||||
var (
|
||||
|
||||
@@ -180,9 +180,9 @@ func TestPrintAppSetSummaryTable(t *testing.T) {
|
||||
Project: default
|
||||
Server:
|
||||
Namespace:
|
||||
Repo:
|
||||
Target:
|
||||
Path:
|
||||
Source:
|
||||
- Repo:
|
||||
Target:
|
||||
SyncPolicy: <none>
|
||||
`,
|
||||
},
|
||||
@@ -193,9 +193,9 @@ SyncPolicy: <none>
|
||||
Project: default
|
||||
Server:
|
||||
Namespace:
|
||||
Repo:
|
||||
Target:
|
||||
Path:
|
||||
Source:
|
||||
- Repo:
|
||||
Target:
|
||||
SyncPolicy: Automated
|
||||
`,
|
||||
},
|
||||
@@ -206,9 +206,9 @@ SyncPolicy: Automated
|
||||
Project: default
|
||||
Server:
|
||||
Namespace:
|
||||
Repo:
|
||||
Target:
|
||||
Path:
|
||||
Source:
|
||||
- Repo:
|
||||
Target:
|
||||
SyncPolicy: Automated
|
||||
`,
|
||||
},
|
||||
|
||||
@@ -111,6 +111,7 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
|
||||
awsAuthConf = &argoappv1.AWSAuthConfig{
|
||||
ClusterName: clusterOpts.AwsClusterName,
|
||||
RoleARN: clusterOpts.AwsRoleArn,
|
||||
Profile: clusterOpts.AwsProfile,
|
||||
}
|
||||
} else if clusterOpts.ExecProviderCommand != "" {
|
||||
execProviderConf = &argoappv1.ExecProviderConfig{
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/redis/go-redis/v9"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/pflag"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
cache2 "k8s.io/client-go/tools/cache"
|
||||
@@ -78,6 +79,12 @@ func (c *forwardCacheClient) Set(item *cache.Item) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (c *forwardCacheClient) Rename(oldKey string, newKey string, expiration time.Duration) error {
|
||||
return c.doLazy(func(client cache.CacheClient) error {
|
||||
return client.Rename(oldKey, newKey, expiration)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *forwardCacheClient) Get(key string, obj interface{}) error {
|
||||
return c.doLazy(func(client cache.CacheClient) error {
|
||||
return client.Get(key, obj)
|
||||
@@ -109,6 +116,7 @@ type forwardRepoClientset struct {
|
||||
repoClientset repoapiclient.Clientset
|
||||
err error
|
||||
repoServerName string
|
||||
kubeClientset kubernetes.Interface
|
||||
}
|
||||
|
||||
func (c *forwardRepoClientset) NewRepoServerClient() (io.Closer, repoapiclient.RepoServerServiceClient, error) {
|
||||
@@ -116,7 +124,19 @@ func (c *forwardRepoClientset) NewRepoServerClient() (io.Closer, repoapiclient.R
|
||||
overrides := clientcmd.ConfigOverrides{
|
||||
CurrentContext: c.context,
|
||||
}
|
||||
repoServerPodLabelSelector := common.LabelKeyAppName + "=" + c.repoServerName
|
||||
repoServerName := c.repoServerName
|
||||
repoServererviceLabelSelector := common.LabelKeyComponentRepoServer + "=" + common.LabelValueComponentRepoServer
|
||||
repoServerServices, err := c.kubeClientset.CoreV1().Services(c.namespace).List(context.Background(), v1.ListOptions{LabelSelector: repoServererviceLabelSelector})
|
||||
if err != nil {
|
||||
c.err = err
|
||||
return
|
||||
}
|
||||
if len(repoServerServices.Items) > 0 {
|
||||
if repoServerServicelabel, ok := repoServerServices.Items[0].Labels[common.LabelKeyAppName]; ok && repoServerServicelabel != "" {
|
||||
repoServerName = repoServerServicelabel
|
||||
}
|
||||
}
|
||||
repoServerPodLabelSelector := common.LabelKeyAppName + "=" + repoServerName
|
||||
repoServerPort, err := kubeutil.PortForward(8081, c.namespace, &overrides, repoServerPodLabelSelector)
|
||||
if err != nil {
|
||||
c.err = err
|
||||
@@ -231,7 +251,7 @@ func MaybeStartLocalServer(ctx context.Context, clientOpts *apiclient.ClientOpti
|
||||
KubeClientset: kubeClientset,
|
||||
Insecure: true,
|
||||
ListenHost: *address,
|
||||
RepoClientset: &forwardRepoClientset{namespace: namespace, context: ctxStr, repoServerName: clientOpts.RepoServerName},
|
||||
RepoClientset: &forwardRepoClientset{namespace: namespace, context: ctxStr, repoServerName: clientOpts.RepoServerName, kubeClientset: kubeClientset},
|
||||
EnableProxyExtension: false,
|
||||
})
|
||||
srv.Init(ctx)
|
||||
|
||||
@@ -78,6 +78,8 @@ func NewProjectCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
command.AddCommand(NewProjectWindowsCommand(clientOpts))
|
||||
command.AddCommand(NewProjectAddOrphanedIgnoreCommand(clientOpts))
|
||||
command.AddCommand(NewProjectRemoveOrphanedIgnoreCommand(clientOpts))
|
||||
command.AddCommand(NewProjectAddSourceNamespace(clientOpts))
|
||||
command.AddCommand(NewProjectRemoveSourceNamespace(clientOpts))
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -509,6 +511,88 @@ func NewProjectAddSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
|
||||
return command
|
||||
}
|
||||
|
||||
// NewProjectAddSourceNamespace returns a new instance of an `argocd proj add-source-namespace` command
|
||||
func NewProjectAddSourceNamespace(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "add-source-namespace PROJECT NAMESPACE",
|
||||
Short: "Add source namespace to the AppProject",
|
||||
Example: templates.Examples(`
|
||||
# Add Kubernetes namespace as source namespace to the AppProject where application resources are allowed to be created in.
|
||||
argocd proj add-source-namespace PROJECT NAMESPACE
|
||||
`),
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
ctx := c.Context()
|
||||
|
||||
if len(args) != 2 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
projName := args[0]
|
||||
srcNamespace := args[1]
|
||||
conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
|
||||
proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
|
||||
errors.CheckError(err)
|
||||
|
||||
for _, item := range proj.Spec.SourceNamespaces {
|
||||
if item == "*" || item == srcNamespace {
|
||||
fmt.Printf("Source namespace '*' already allowed in project\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
proj.Spec.SourceNamespaces = append(proj.Spec.SourceNamespaces, srcNamespace)
|
||||
_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
|
||||
errors.CheckError(err)
|
||||
},
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
// NewProjectRemoveSourceNamespace returns a new instance of an `argocd proj remove-source-namespace` command
|
||||
func NewProjectRemoveSourceNamespace(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "remove-source-namespace PROJECT NAMESPACE",
|
||||
Short: "Removes the source namespace from the AppProject",
|
||||
Example: templates.Examples(`
|
||||
# Remove source NAMESPACE in PROJECT
|
||||
argocd proj remove-source-namespace PROJECT NAMESPACE
|
||||
`),
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
ctx := c.Context()
|
||||
|
||||
if len(args) != 2 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
projName := args[0]
|
||||
srcNamespace := args[1]
|
||||
conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
|
||||
proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
|
||||
errors.CheckError(err)
|
||||
|
||||
index := -1
|
||||
for i, item := range proj.Spec.SourceNamespaces {
|
||||
if item == srcNamespace && item != "*" {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index == -1 {
|
||||
fmt.Printf("Source namespace '%s' does not exist in project or cannot be removed\n", srcNamespace)
|
||||
} else {
|
||||
proj.Spec.SourceNamespaces = append(proj.Spec.SourceNamespaces[:index], proj.Spec.SourceNamespaces[index+1:]...)
|
||||
_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
|
||||
errors.CheckError(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
func modifyResourcesList(list *[]metav1.GroupKind, add bool, listDesc string, group string, kind string) bool {
|
||||
if add {
|
||||
for _, item := range *list {
|
||||
|
||||
@@ -64,6 +64,12 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
# Add a Git repository via SSH on a non-default port - need to use ssh:// style URLs here
|
||||
argocd repo add ssh://git@git.example.com:2222/repos/repo --ssh-private-key-path ~/id_rsa
|
||||
|
||||
# Add a Git repository via SSH using socks5 proxy with no proxy credentials
|
||||
argocd repo add ssh://git@github.com/argoproj/argocd-example-apps --ssh-private-key-path ~/id_rsa --proxy socks5://your.proxy.server.ip:1080
|
||||
|
||||
# Add a Git repository via SSH using socks5 proxy with proxy credentials
|
||||
argocd repo add ssh://git@github.com/argoproj/argocd-example-apps --ssh-private-key-path ~/id_rsa --proxy socks5://username:password@your.proxy.server.ip:1080
|
||||
|
||||
# Add a private Git repository via HTTPS using username/password and TLS client certificates:
|
||||
argocd repo add https://git.example.com/repos/repo --username git --password secret --tls-client-cert-path ~/mycert.crt --tls-client-cert-key-path ~/mycert.key
|
||||
|
||||
|
||||
255
cmd/util/app.go
@@ -68,6 +68,7 @@ type AppOptions struct {
|
||||
kustomizeVersion string
|
||||
kustomizeCommonLabels []string
|
||||
kustomizeCommonAnnotations []string
|
||||
kustomizeLabelWithoutSelector bool
|
||||
kustomizeForceCommonLabels bool
|
||||
kustomizeForceCommonAnnotations bool
|
||||
kustomizeNamespace string
|
||||
@@ -79,6 +80,7 @@ type AppOptions struct {
|
||||
retryBackoffDuration time.Duration
|
||||
retryBackoffMaxDuration time.Duration
|
||||
retryBackoffFactor int64
|
||||
ref string
|
||||
}
|
||||
|
||||
func AddAppFlags(command *cobra.Command, opts *AppOptions) {
|
||||
@@ -103,7 +105,7 @@ func AddAppFlags(command *cobra.Command, opts *AppOptions) {
|
||||
command.Flags().StringArrayVar(&opts.helmSetFiles, "helm-set-file", []string{}, "Helm set values from respective files specified via the command line (can be repeated to set several values: --helm-set-file key1=path1 --helm-set-file key2=path2)")
|
||||
command.Flags().BoolVar(&opts.helmSkipCrds, "helm-skip-crds", false, "Skip helm crd installation step")
|
||||
command.Flags().StringVar(&opts.project, "project", "", "Application project name")
|
||||
command.Flags().StringVar(&opts.syncPolicy, "sync-policy", "", "Set the sync policy (one of: none, automated (aliases of automated: auto, automatic))")
|
||||
command.Flags().StringVar(&opts.syncPolicy, "sync-policy", "", "Set the sync policy (one of: manual (aliases of manual: none), automated (aliases of automated: auto, automatic))")
|
||||
command.Flags().StringArrayVar(&opts.syncOptions, "sync-option", []string{}, "Add or remove a sync option, e.g add `Prune=false`. Remove using `!` prefix, e.g. `!Prune=false`")
|
||||
command.Flags().BoolVar(&opts.autoPrune, "auto-prune", false, "Set automatic pruning when sync is automated")
|
||||
command.Flags().BoolVar(&opts.selfHeal, "self-heal", false, "Set self healing when sync is automated")
|
||||
@@ -124,6 +126,7 @@ func AddAppFlags(command *cobra.Command, opts *AppOptions) {
|
||||
command.Flags().BoolVar(&opts.Validate, "validate", true, "Validation of repo and cluster")
|
||||
command.Flags().StringArrayVar(&opts.kustomizeCommonLabels, "kustomize-common-label", []string{}, "Set common labels in Kustomize")
|
||||
command.Flags().StringArrayVar(&opts.kustomizeCommonAnnotations, "kustomize-common-annotation", []string{}, "Set common labels in Kustomize")
|
||||
command.Flags().BoolVar(&opts.kustomizeLabelWithoutSelector, "kustomize-label-without-selector", false, "Do not apply common label to selectors or templates")
|
||||
command.Flags().BoolVar(&opts.kustomizeForceCommonLabels, "kustomize-force-common-label", false, "Force common labels in Kustomize")
|
||||
command.Flags().BoolVar(&opts.kustomizeForceCommonAnnotations, "kustomize-force-common-annotation", false, "Force common annotations in Kustomize")
|
||||
command.Flags().StringVar(&opts.kustomizeNamespace, "kustomize-namespace", "", "Kustomize namespace")
|
||||
@@ -133,81 +136,37 @@ func AddAppFlags(command *cobra.Command, opts *AppOptions) {
|
||||
command.Flags().DurationVar(&opts.retryBackoffDuration, "sync-retry-backoff-duration", argoappv1.DefaultSyncRetryDuration, "Sync retry backoff base duration. Input needs to be a duration (e.g. 2m, 1h)")
|
||||
command.Flags().DurationVar(&opts.retryBackoffMaxDuration, "sync-retry-backoff-max-duration", argoappv1.DefaultSyncRetryMaxDuration, "Max sync retry backoff duration. Input needs to be a duration (e.g. 2m, 1h)")
|
||||
command.Flags().Int64Var(&opts.retryBackoffFactor, "sync-retry-backoff-factor", argoappv1.DefaultSyncRetryFactor, "Factor multiplies the base duration after each failed sync retry")
|
||||
command.Flags().StringVar(&opts.ref, "ref", "", "Ref is reference to another source within sources field")
|
||||
}
|
||||
|
||||
func SetAppSpecOptions(flags *pflag.FlagSet, spec *argoappv1.ApplicationSpec, appOpts *AppOptions) int {
|
||||
func SetAppSpecOptions(flags *pflag.FlagSet, spec *argoappv1.ApplicationSpec, appOpts *AppOptions, sourcePosition int) int {
|
||||
visited := 0
|
||||
if flags == nil {
|
||||
return visited
|
||||
}
|
||||
source := spec.GetSourcePtrByPosition(sourcePosition)
|
||||
if source == nil {
|
||||
source = &argoappv1.ApplicationSource{}
|
||||
}
|
||||
source, visited = ConstructSource(source, *appOpts, flags)
|
||||
if spec.HasMultipleSources() {
|
||||
if sourcePosition == 0 {
|
||||
spec.Sources[sourcePosition] = *source
|
||||
} else if sourcePosition > 0 {
|
||||
spec.Sources[sourcePosition-1] = *source
|
||||
} else {
|
||||
spec.Sources = append(spec.Sources, *source)
|
||||
}
|
||||
} else {
|
||||
spec.Source = source
|
||||
}
|
||||
flags.Visit(func(f *pflag.Flag) {
|
||||
visited++
|
||||
source := spec.GetSourcePtr()
|
||||
if source == nil {
|
||||
source = &argoappv1.ApplicationSource{}
|
||||
}
|
||||
|
||||
switch f.Name {
|
||||
case "repo":
|
||||
source.RepoURL = appOpts.repoURL
|
||||
case "path":
|
||||
source.Path = appOpts.appPath
|
||||
case "helm-chart":
|
||||
source.Chart = appOpts.chart
|
||||
case "revision":
|
||||
source.TargetRevision = appOpts.revision
|
||||
case "revision-history-limit":
|
||||
i := int64(appOpts.revisionHistoryLimit)
|
||||
spec.RevisionHistoryLimit = &i
|
||||
case "values":
|
||||
setHelmOpt(source, helmOpts{valueFiles: appOpts.valuesFiles})
|
||||
case "ignore-missing-value-files":
|
||||
setHelmOpt(source, helmOpts{ignoreMissingValueFiles: appOpts.ignoreMissingValueFiles})
|
||||
case "values-literal-file":
|
||||
var data []byte
|
||||
|
||||
// read uri
|
||||
parsedURL, err := url.ParseRequestURI(appOpts.values)
|
||||
if err != nil || !(parsedURL.Scheme == "http" || parsedURL.Scheme == "https") {
|
||||
data, err = os.ReadFile(appOpts.values)
|
||||
} else {
|
||||
data, err = config.ReadRemoteFile(appOpts.values)
|
||||
}
|
||||
errors.CheckError(err)
|
||||
setHelmOpt(source, helmOpts{values: string(data)})
|
||||
case "release-name":
|
||||
setHelmOpt(source, helmOpts{releaseName: appOpts.releaseName})
|
||||
case "helm-version":
|
||||
setHelmOpt(source, helmOpts{version: appOpts.helmVersion})
|
||||
case "helm-pass-credentials":
|
||||
setHelmOpt(source, helmOpts{passCredentials: appOpts.helmPassCredentials})
|
||||
case "helm-set":
|
||||
setHelmOpt(source, helmOpts{helmSets: appOpts.helmSets})
|
||||
case "helm-set-string":
|
||||
setHelmOpt(source, helmOpts{helmSetStrings: appOpts.helmSetStrings})
|
||||
case "helm-set-file":
|
||||
setHelmOpt(source, helmOpts{helmSetFiles: appOpts.helmSetFiles})
|
||||
case "helm-skip-crds":
|
||||
setHelmOpt(source, helmOpts{skipCrds: appOpts.helmSkipCrds})
|
||||
case "directory-recurse":
|
||||
if source.Directory != nil {
|
||||
source.Directory.Recurse = appOpts.directoryRecurse
|
||||
} else {
|
||||
source.Directory = &argoappv1.ApplicationSourceDirectory{Recurse: appOpts.directoryRecurse}
|
||||
}
|
||||
case "directory-exclude":
|
||||
if source.Directory != nil {
|
||||
source.Directory.Exclude = appOpts.directoryExclude
|
||||
} else {
|
||||
source.Directory = &argoappv1.ApplicationSourceDirectory{Exclude: appOpts.directoryExclude}
|
||||
}
|
||||
case "directory-include":
|
||||
if source.Directory != nil {
|
||||
source.Directory.Include = appOpts.directoryInclude
|
||||
} else {
|
||||
source.Directory = &argoappv1.ApplicationSourceDirectory{Include: appOpts.directoryInclude}
|
||||
}
|
||||
case "config-management-plugin":
|
||||
source.Plugin = &argoappv1.ApplicationSourcePlugin{Name: appOpts.configManagementPlugin}
|
||||
case "dest-name":
|
||||
spec.Destination.Name = appOpts.destName
|
||||
case "dest-server":
|
||||
@@ -216,45 +175,9 @@ func SetAppSpecOptions(flags *pflag.FlagSet, spec *argoappv1.ApplicationSpec, ap
|
||||
spec.Destination.Namespace = appOpts.destNamespace
|
||||
case "project":
|
||||
spec.Project = appOpts.project
|
||||
case "nameprefix":
|
||||
setKustomizeOpt(source, kustomizeOpts{namePrefix: appOpts.namePrefix})
|
||||
case "namesuffix":
|
||||
setKustomizeOpt(source, kustomizeOpts{nameSuffix: appOpts.nameSuffix})
|
||||
case "kustomize-image":
|
||||
setKustomizeOpt(source, kustomizeOpts{images: appOpts.kustomizeImages})
|
||||
case "kustomize-replica":
|
||||
setKustomizeOpt(source, kustomizeOpts{replicas: appOpts.kustomizeReplicas})
|
||||
case "kustomize-version":
|
||||
setKustomizeOpt(source, kustomizeOpts{version: appOpts.kustomizeVersion})
|
||||
case "kustomize-namespace":
|
||||
setKustomizeOpt(source, kustomizeOpts{namespace: appOpts.kustomizeNamespace})
|
||||
case "kustomize-common-label":
|
||||
parsedLabels, err := label.Parse(appOpts.kustomizeCommonLabels)
|
||||
errors.CheckError(err)
|
||||
setKustomizeOpt(source, kustomizeOpts{commonLabels: parsedLabels})
|
||||
case "kustomize-common-annotation":
|
||||
parsedAnnotations, err := label.Parse(appOpts.kustomizeCommonAnnotations)
|
||||
errors.CheckError(err)
|
||||
setKustomizeOpt(source, kustomizeOpts{commonAnnotations: parsedAnnotations})
|
||||
case "kustomize-force-common-label":
|
||||
setKustomizeOpt(source, kustomizeOpts{forceCommonLabels: appOpts.kustomizeForceCommonLabels})
|
||||
case "kustomize-force-common-annotation":
|
||||
setKustomizeOpt(source, kustomizeOpts{forceCommonAnnotations: appOpts.kustomizeForceCommonAnnotations})
|
||||
case "jsonnet-tla-str":
|
||||
setJsonnetOpt(source, appOpts.jsonnetTlaStr, false)
|
||||
case "jsonnet-tla-code":
|
||||
setJsonnetOpt(source, appOpts.jsonnetTlaCode, true)
|
||||
case "jsonnet-ext-var-str":
|
||||
setJsonnetOptExtVar(source, appOpts.jsonnetExtVarStr, false)
|
||||
case "jsonnet-ext-var-code":
|
||||
setJsonnetOptExtVar(source, appOpts.jsonnetExtVarCode, true)
|
||||
case "jsonnet-libs":
|
||||
setJsonnetOptLibs(source, appOpts.jsonnetLibs)
|
||||
case "plugin-env":
|
||||
setPluginOptEnvs(source, appOpts.pluginEnvs)
|
||||
case "sync-policy":
|
||||
switch appOpts.syncPolicy {
|
||||
case "none":
|
||||
case "none", "manual":
|
||||
if spec.SyncPolicy != nil {
|
||||
spec.SyncPolicy.Automated = nil
|
||||
}
|
||||
@@ -308,7 +231,6 @@ func SetAppSpecOptions(flags *pflag.FlagSet, spec *argoappv1.ApplicationSpec, ap
|
||||
log.Fatalf("Invalid sync-retry-limit [%d]", appOpts.retryLimit)
|
||||
}
|
||||
}
|
||||
spec.Source = source
|
||||
})
|
||||
if flags.Changed("auto-prune") {
|
||||
if spec.SyncPolicy == nil || spec.SyncPolicy.Automated == nil {
|
||||
@@ -340,6 +262,7 @@ type kustomizeOpts struct {
|
||||
version string
|
||||
commonLabels map[string]string
|
||||
commonAnnotations map[string]string
|
||||
labelWithoutSelector bool
|
||||
forceCommonLabels bool
|
||||
forceCommonAnnotations bool
|
||||
namespace string
|
||||
@@ -367,6 +290,9 @@ func setKustomizeOpt(src *argoappv1.ApplicationSource, opts kustomizeOpts) {
|
||||
if opts.commonAnnotations != nil {
|
||||
src.Kustomize.CommonAnnotations = opts.commonAnnotations
|
||||
}
|
||||
if opts.labelWithoutSelector {
|
||||
src.Kustomize.LabelWithoutSelector = opts.labelWithoutSelector
|
||||
}
|
||||
if opts.forceCommonLabels {
|
||||
src.Kustomize.ForceCommonLabels = opts.forceCommonLabels
|
||||
}
|
||||
@@ -498,11 +424,11 @@ func setJsonnetOptLibs(src *argoappv1.ApplicationSource, libs []string) {
|
||||
// SetParameterOverrides updates an existing or appends a new parameter override in the application
|
||||
// The app is assumed to be a helm app and is expected to be in the form:
|
||||
// param=value
|
||||
func SetParameterOverrides(app *argoappv1.Application, parameters []string) {
|
||||
func SetParameterOverrides(app *argoappv1.Application, parameters []string, index int) {
|
||||
if len(parameters) == 0 {
|
||||
return
|
||||
}
|
||||
source := app.Spec.GetSource()
|
||||
source := app.Spec.GetSourcePtrByIndex(index)
|
||||
var sourceType argoappv1.ApplicationSourceType
|
||||
if st, _ := source.ExplicitType(); st != nil {
|
||||
sourceType = *st
|
||||
@@ -614,8 +540,8 @@ func constructAppsBaseOnName(appName string, labels, annotations, args []string,
|
||||
Source: &argoappv1.ApplicationSource{},
|
||||
},
|
||||
}
|
||||
SetAppSpecOptions(flags, &app.Spec, &appOpts)
|
||||
SetParameterOverrides(app, appOpts.Parameters)
|
||||
SetAppSpecOptions(flags, &app.Spec, &appOpts, 0)
|
||||
SetParameterOverrides(app, appOpts.Parameters, 0)
|
||||
mergeLabels(app, labels)
|
||||
setAnnotations(app, annotations)
|
||||
return []*argoappv1.Application{
|
||||
@@ -640,10 +566,15 @@ func constructAppsFromFileUrl(fileURL, appName string, labels, annotations, args
|
||||
if app.Name == "" {
|
||||
return nil, fmt.Errorf("app.Name is empty. --name argument can be used to provide app.Name")
|
||||
}
|
||||
SetAppSpecOptions(flags, &app.Spec, &appOpts)
|
||||
SetParameterOverrides(app, appOpts.Parameters)
|
||||
|
||||
mergeLabels(app, labels)
|
||||
setAnnotations(app, annotations)
|
||||
|
||||
// do not allow overrides for applications with multiple sources
|
||||
if !app.Spec.HasMultipleSources() {
|
||||
SetAppSpecOptions(flags, &app.Spec, &appOpts, 0)
|
||||
SetParameterOverrides(app, appOpts.Parameters, 0)
|
||||
}
|
||||
}
|
||||
return apps, nil
|
||||
}
|
||||
@@ -654,9 +585,117 @@ func ConstructApps(fileURL, appName string, labels, annotations, args []string,
|
||||
} else if fileURL != "" {
|
||||
return constructAppsFromFileUrl(fileURL, appName, labels, annotations, args, appOpts, flags)
|
||||
}
|
||||
|
||||
return constructAppsBaseOnName(appName, labels, annotations, args, appOpts, flags)
|
||||
}
|
||||
|
||||
func ConstructSource(source *argoappv1.ApplicationSource, appOpts AppOptions, flags *pflag.FlagSet) (*argoappv1.ApplicationSource, int) {
|
||||
visited := 0
|
||||
flags.Visit(func(f *pflag.Flag) {
|
||||
visited++
|
||||
switch f.Name {
|
||||
case "repo":
|
||||
source.RepoURL = appOpts.repoURL
|
||||
case "path":
|
||||
source.Path = appOpts.appPath
|
||||
case "helm-chart":
|
||||
source.Chart = appOpts.chart
|
||||
case "revision":
|
||||
source.TargetRevision = appOpts.revision
|
||||
case "values":
|
||||
setHelmOpt(source, helmOpts{valueFiles: appOpts.valuesFiles})
|
||||
case "ignore-missing-value-files":
|
||||
setHelmOpt(source, helmOpts{ignoreMissingValueFiles: appOpts.ignoreMissingValueFiles})
|
||||
case "values-literal-file":
|
||||
var data []byte
|
||||
// read uri
|
||||
parsedURL, err := url.ParseRequestURI(appOpts.values)
|
||||
if err != nil || !(parsedURL.Scheme == "http" || parsedURL.Scheme == "https") {
|
||||
data, err = os.ReadFile(appOpts.values)
|
||||
} else {
|
||||
data, err = config.ReadRemoteFile(appOpts.values)
|
||||
}
|
||||
errors.CheckError(err)
|
||||
setHelmOpt(source, helmOpts{values: string(data)})
|
||||
case "release-name":
|
||||
setHelmOpt(source, helmOpts{releaseName: appOpts.releaseName})
|
||||
case "helm-version":
|
||||
setHelmOpt(source, helmOpts{version: appOpts.helmVersion})
|
||||
case "helm-pass-credentials":
|
||||
setHelmOpt(source, helmOpts{passCredentials: appOpts.helmPassCredentials})
|
||||
case "helm-set":
|
||||
setHelmOpt(source, helmOpts{helmSets: appOpts.helmSets})
|
||||
case "helm-set-string":
|
||||
setHelmOpt(source, helmOpts{helmSetStrings: appOpts.helmSetStrings})
|
||||
case "helm-set-file":
|
||||
setHelmOpt(source, helmOpts{helmSetFiles: appOpts.helmSetFiles})
|
||||
case "helm-skip-crds":
|
||||
setHelmOpt(source, helmOpts{skipCrds: appOpts.helmSkipCrds})
|
||||
case "directory-recurse":
|
||||
if source.Directory != nil {
|
||||
source.Directory.Recurse = appOpts.directoryRecurse
|
||||
} else {
|
||||
source.Directory = &argoappv1.ApplicationSourceDirectory{Recurse: appOpts.directoryRecurse}
|
||||
}
|
||||
case "directory-exclude":
|
||||
if source.Directory != nil {
|
||||
source.Directory.Exclude = appOpts.directoryExclude
|
||||
} else {
|
||||
source.Directory = &argoappv1.ApplicationSourceDirectory{Exclude: appOpts.directoryExclude}
|
||||
}
|
||||
case "directory-include":
|
||||
if source.Directory != nil {
|
||||
source.Directory.Include = appOpts.directoryInclude
|
||||
} else {
|
||||
source.Directory = &argoappv1.ApplicationSourceDirectory{Include: appOpts.directoryInclude}
|
||||
}
|
||||
case "config-management-plugin":
|
||||
source.Plugin = &argoappv1.ApplicationSourcePlugin{Name: appOpts.configManagementPlugin}
|
||||
case "nameprefix":
|
||||
setKustomizeOpt(source, kustomizeOpts{namePrefix: appOpts.namePrefix})
|
||||
case "namesuffix":
|
||||
setKustomizeOpt(source, kustomizeOpts{nameSuffix: appOpts.nameSuffix})
|
||||
case "kustomize-image":
|
||||
setKustomizeOpt(source, kustomizeOpts{images: appOpts.kustomizeImages})
|
||||
case "kustomize-replica":
|
||||
setKustomizeOpt(source, kustomizeOpts{replicas: appOpts.kustomizeReplicas})
|
||||
case "kustomize-version":
|
||||
setKustomizeOpt(source, kustomizeOpts{version: appOpts.kustomizeVersion})
|
||||
case "kustomize-namespace":
|
||||
setKustomizeOpt(source, kustomizeOpts{namespace: appOpts.kustomizeNamespace})
|
||||
case "kustomize-common-label":
|
||||
parsedLabels, err := label.Parse(appOpts.kustomizeCommonLabels)
|
||||
errors.CheckError(err)
|
||||
setKustomizeOpt(source, kustomizeOpts{commonLabels: parsedLabels})
|
||||
case "kustomize-common-annotation":
|
||||
parsedAnnotations, err := label.Parse(appOpts.kustomizeCommonAnnotations)
|
||||
errors.CheckError(err)
|
||||
setKustomizeOpt(source, kustomizeOpts{commonAnnotations: parsedAnnotations})
|
||||
case "kustomize-label-without-selector":
|
||||
setKustomizeOpt(source, kustomizeOpts{labelWithoutSelector: appOpts.kustomizeLabelWithoutSelector})
|
||||
case "kustomize-force-common-label":
|
||||
setKustomizeOpt(source, kustomizeOpts{forceCommonLabels: appOpts.kustomizeForceCommonLabels})
|
||||
case "kustomize-force-common-annotation":
|
||||
setKustomizeOpt(source, kustomizeOpts{forceCommonAnnotations: appOpts.kustomizeForceCommonAnnotations})
|
||||
case "jsonnet-tla-str":
|
||||
setJsonnetOpt(source, appOpts.jsonnetTlaStr, false)
|
||||
case "jsonnet-tla-code":
|
||||
setJsonnetOpt(source, appOpts.jsonnetTlaCode, true)
|
||||
case "jsonnet-ext-var-str":
|
||||
setJsonnetOptExtVar(source, appOpts.jsonnetExtVarStr, false)
|
||||
case "jsonnet-ext-var-code":
|
||||
setJsonnetOptExtVar(source, appOpts.jsonnetExtVarCode, true)
|
||||
case "jsonnet-libs":
|
||||
setJsonnetOptLibs(source, appOpts.jsonnetLibs)
|
||||
case "plugin-env":
|
||||
setPluginOptEnvs(source, appOpts.pluginEnvs)
|
||||
case "ref":
|
||||
source.Ref = appOpts.ref
|
||||
}
|
||||
})
|
||||
return source, visited
|
||||
}
|
||||
|
||||
func mergeLabels(app *argoappv1.Application, labels []string) {
|
||||
mapLabels, err := label.Parse(labels)
|
||||
errors.CheckError(err)
|
||||
|
||||
@@ -123,6 +123,11 @@ func Test_setKustomizeOpt(t *testing.T) {
|
||||
setKustomizeOpt(&src, kustomizeOpts{commonAnnotations: map[string]string{"foo1": "bar1", "foo2": "bar2"}})
|
||||
assert.Equal(t, &v1alpha1.ApplicationSourceKustomize{CommonAnnotations: map[string]string{"foo1": "bar1", "foo2": "bar2"}}, src.Kustomize)
|
||||
})
|
||||
t.Run("Label Without Selector", func(t *testing.T) {
|
||||
src := v1alpha1.ApplicationSource{}
|
||||
setKustomizeOpt(&src, kustomizeOpts{commonLabels: map[string]string{"foo1": "bar1", "foo2": "bar2"}, labelWithoutSelector: true})
|
||||
assert.Equal(t, &v1alpha1.ApplicationSourceKustomize{CommonLabels: map[string]string{"foo1": "bar1", "foo2": "bar2"}, LabelWithoutSelector: true}, src.Kustomize)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_setJsonnetOpt(t *testing.T) {
|
||||
@@ -165,7 +170,16 @@ func (f *appOptionsFixture) SetFlag(key, value string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = SetAppSpecOptions(f.command.Flags(), f.spec, f.options)
|
||||
_ = SetAppSpecOptions(f.command.Flags(), f.spec, f.options, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *appOptionsFixture) SetFlagWithSourcePosition(key, value string, sourcePosition int) error {
|
||||
err := f.command.Flags().Set(key, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = SetAppSpecOptions(f.command.Flags(), f.spec, f.options, sourcePosition)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -220,6 +234,54 @@ func Test_setAppSpecOptions(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func newMultiSourceAppOptionsFixture() *appOptionsFixture {
|
||||
fixture := &appOptionsFixture{
|
||||
spec: &v1alpha1.ApplicationSpec{
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
v1alpha1.ApplicationSource{},
|
||||
v1alpha1.ApplicationSource{},
|
||||
},
|
||||
},
|
||||
command: &cobra.Command{},
|
||||
options: &AppOptions{},
|
||||
}
|
||||
AddAppFlags(fixture.command, fixture.options)
|
||||
return fixture
|
||||
}
|
||||
|
||||
func Test_setAppSpecOptionsMultiSourceApp(t *testing.T) {
|
||||
f := newMultiSourceAppOptionsFixture()
|
||||
sourcePosition := 0
|
||||
sourcePosition1 := 1
|
||||
sourcePosition2 := 2
|
||||
t.Run("SyncPolicy", func(t *testing.T) {
|
||||
assert.NoError(t, f.SetFlagWithSourcePosition("sync-policy", "automated", sourcePosition1))
|
||||
assert.NotNil(t, f.spec.SyncPolicy.Automated)
|
||||
|
||||
f.spec.SyncPolicy = nil
|
||||
assert.NoError(t, f.SetFlagWithSourcePosition("sync-policy", "automatic", sourcePosition1))
|
||||
assert.NotNil(t, f.spec.SyncPolicy.Automated)
|
||||
})
|
||||
t.Run("Helm - SourcePosition 0", func(t *testing.T) {
|
||||
assert.NoError(t, f.SetFlagWithSourcePosition("helm-version", "v2", sourcePosition))
|
||||
assert.Equal(t, len(f.spec.GetSources()), 2)
|
||||
assert.Equal(t, f.spec.GetSources()[sourcePosition].Helm.Version, "v2")
|
||||
})
|
||||
t.Run("Kustomize", func(t *testing.T) {
|
||||
assert.NoError(t, f.SetFlagWithSourcePosition("kustomize-replica", "my-deployment=2", sourcePosition1))
|
||||
assert.Equal(t, f.spec.Sources[sourcePosition1-1].Kustomize.Replicas, v1alpha1.KustomizeReplicas{{Name: "my-deployment", Count: intstr.FromInt(2)}})
|
||||
assert.NoError(t, f.SetFlagWithSourcePosition("kustomize-replica", "my-deployment=4", sourcePosition2))
|
||||
assert.Equal(t, f.spec.Sources[sourcePosition2-1].Kustomize.Replicas, v1alpha1.KustomizeReplicas{{Name: "my-deployment", Count: intstr.FromInt(4)}})
|
||||
})
|
||||
t.Run("Helm", func(t *testing.T) {
|
||||
assert.NoError(t, f.SetFlagWithSourcePosition("helm-version", "v2", sourcePosition1))
|
||||
assert.NoError(t, f.SetFlagWithSourcePosition("helm-version", "v3", sourcePosition2))
|
||||
assert.Equal(t, len(f.spec.GetSources()), 2)
|
||||
assert.Equal(t, f.spec.GetSources()[sourcePosition1-1].Helm.Version, "v2")
|
||||
assert.Equal(t, f.spec.GetSources()[sourcePosition2-1].Helm.Version, "v3")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_setAnnotations(t *testing.T) {
|
||||
t.Run("Annotations", func(t *testing.T) {
|
||||
app := v1alpha1.Application{}
|
||||
|
||||
@@ -144,6 +144,7 @@ type ClusterOptions struct {
|
||||
Upsert bool
|
||||
ServiceAccount string
|
||||
AwsRoleArn string
|
||||
AwsProfile string
|
||||
AwsClusterName string
|
||||
SystemNamespace string
|
||||
Namespaces []string
|
||||
@@ -169,6 +170,7 @@ func AddClusterFlags(command *cobra.Command, opts *ClusterOptions) {
|
||||
command.Flags().BoolVar(&opts.InCluster, "in-cluster", false, "Indicates Argo CD resides inside this cluster and should connect using the internal k8s hostname (kubernetes.default.svc)")
|
||||
command.Flags().StringVar(&opts.AwsClusterName, "aws-cluster-name", "", "AWS Cluster name if set then aws cli eks token command will be used to access cluster")
|
||||
command.Flags().StringVar(&opts.AwsRoleArn, "aws-role-arn", "", "Optional AWS role arn. If set then AWS IAM Authenticator assumes a role to perform cluster operations instead of the default AWS credential provider chain.")
|
||||
command.Flags().StringVar(&opts.AwsProfile, "aws-profile", "", "Optional AWS profile. If set then AWS IAM Authenticator uses this profile to perform cluster operations instead of the default AWS credential provider chain.")
|
||||
command.Flags().StringArrayVar(&opts.Namespaces, "namespace", nil, "List of namespaces which are allowed to manage")
|
||||
command.Flags().BoolVar(&opts.ClusterResources, "cluster-resources", false, "Indicates if cluster level resources should be managed. The setting is used only if list of managed namespaces is not empty.")
|
||||
command.Flags().StringVar(&opts.Name, "name", "", "Overwrite the cluster name")
|
||||
|
||||
@@ -46,13 +46,13 @@ func NewServer(initConstants plugin.CMPServerInitConstants) (*ArgoCDCMPServer, e
|
||||
|
||||
serverLog := log.NewEntry(log.StandardLogger())
|
||||
streamInterceptors := []grpc.StreamServerInterceptor{
|
||||
otelgrpc.StreamServerInterceptor(),
|
||||
otelgrpc.StreamServerInterceptor(), //nolint:staticcheck // TODO: ignore SA1019 for depreciation: see https://github.com/argoproj/argo-cd/issues/18258
|
||||
grpc_logrus.StreamServerInterceptor(serverLog),
|
||||
grpc_prometheus.StreamServerInterceptor,
|
||||
grpc_util.PanicLoggerStreamServerInterceptor(serverLog),
|
||||
}
|
||||
unaryInterceptors := []grpc.UnaryServerInterceptor{
|
||||
otelgrpc.UnaryServerInterceptor(),
|
||||
otelgrpc.UnaryServerInterceptor(), //nolint:staticcheck // TODO: ignore SA1019 for depreciation: see https://github.com/argoproj/argo-cd/issues/18258
|
||||
grpc_logrus.UnaryServerInterceptor(serverLog),
|
||||
grpc_prometheus.UnaryServerInterceptor,
|
||||
grpc_util.PanicLoggerUnaryServerInterceptor(serverLog),
|
||||
|
||||
@@ -115,9 +115,9 @@ const (
|
||||
LegacyShardingAlgorithm = "legacy"
|
||||
// RoundRobinShardingAlgorithm is a flag value that can be opted for Sharding Algorithm it uses an equal distribution accross all shards
|
||||
RoundRobinShardingAlgorithm = "round-robin"
|
||||
DefaultShardingAlgorithm = LegacyShardingAlgorithm
|
||||
// AppControllerHeartbeatUpdateRetryCount is the retry count for updating the Shard Mapping to the Shard Mapping ConfigMap used by Application Controller
|
||||
AppControllerHeartbeatUpdateRetryCount = 3
|
||||
DefaultShardingAlgorithm = LegacyShardingAlgorithm
|
||||
)
|
||||
|
||||
// Dex related constants
|
||||
@@ -149,10 +149,14 @@ const (
|
||||
LabelKeyAppInstance = "app.kubernetes.io/instance"
|
||||
// LabelKeyAppName is the label key to use to uniquely identify the name of the Kubernetes application
|
||||
LabelKeyAppName = "app.kubernetes.io/name"
|
||||
// LabelKeyAutoLabelClusterInfo if set to true will automatically add extra labels from the cluster info (currently it only adds a k8s version label)
|
||||
LabelKeyAutoLabelClusterInfo = "argocd.argoproj.io/auto-label-cluster-info"
|
||||
// LabelKeyLegacyApplicationName is the legacy label (v0.10 and below) and is superseded by 'app.kubernetes.io/instance'
|
||||
LabelKeyLegacyApplicationName = "applications.argoproj.io/app-name"
|
||||
// LabelKeySecretType contains the type of argocd secret (currently: 'cluster', 'repository', 'repo-config' or 'repo-creds')
|
||||
LabelKeySecretType = "argocd.argoproj.io/secret-type"
|
||||
// LabelKeyClusterKubernetesVersion contains the kubernetes version of the cluster secret if it has been enabled
|
||||
LabelKeyClusterKubernetesVersion = "argocd.argoproj.io/kubernetes-version"
|
||||
// LabelValueSecretTypeCluster indicates a secret type of cluster
|
||||
LabelValueSecretTypeCluster = "cluster"
|
||||
// LabelValueSecretTypeRepository indicates a secret type of repository
|
||||
@@ -184,6 +188,10 @@ const (
|
||||
// AnnotationKeyAppSkipReconcile tells the Application to skip the Application controller reconcile.
|
||||
// Skip reconcile when the value is "true" or any other string values that can be strconv.ParseBool() to be true.
|
||||
AnnotationKeyAppSkipReconcile = "argocd.argoproj.io/skip-reconcile"
|
||||
// LabelKeyComponentRepoServer is the label key to identify the component as repo-server
|
||||
LabelKeyComponentRepoServer = "app.kubernetes.io/component"
|
||||
// LabelValueComponentRepoServer is the label value for the repo-server component
|
||||
LabelValueComponentRepoServer = "repo-server"
|
||||
)
|
||||
|
||||
// Environment variables for tuning and debugging Argo CD
|
||||
@@ -238,6 +246,8 @@ const (
|
||||
EnvLogFormat = "ARGOCD_LOG_FORMAT"
|
||||
// EnvLogLevel log level that is defined by `--loglevel` option
|
||||
EnvLogLevel = "ARGOCD_LOG_LEVEL"
|
||||
// EnvLogFormatEnableFullTimestamp enables the FullTimestamp option in logs
|
||||
EnvLogFormatEnableFullTimestamp = "ARGOCD_LOG_FORMAT_ENABLE_FULL_TIMESTAMP"
|
||||
// EnvMaxCookieNumber max number of chunks a cookie can be broken into
|
||||
EnvMaxCookieNumber = "ARGOCD_MAX_COOKIE_NUMBER"
|
||||
// EnvPluginSockFilePath allows to override the pluginSockFilePath for repo server and cmp server
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
@@ -47,13 +48,13 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/controller/sharding"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
|
||||
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
argov1alpha "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions/application/v1alpha1"
|
||||
applisters "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
argodiff "github.com/argoproj/argo-cd/v2/util/argo/diff"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
"github.com/argoproj/argo-cd/v2/util/env"
|
||||
|
||||
kubeerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -113,11 +114,11 @@ type ApplicationController struct {
|
||||
appInformer cache.SharedIndexInformer
|
||||
appLister applisters.ApplicationLister
|
||||
projInformer cache.SharedIndexInformer
|
||||
deploymentInformer informerv1.DeploymentInformer
|
||||
appStateManager AppStateManager
|
||||
stateCache statecache.LiveStateCache
|
||||
statusRefreshTimeout time.Duration
|
||||
statusHardRefreshTimeout time.Duration
|
||||
statusRefreshJitter time.Duration
|
||||
selfHealTimeout time.Duration
|
||||
repoClientset apiclient.Clientset
|
||||
db db.ArgoDB
|
||||
@@ -126,9 +127,14 @@ type ApplicationController struct {
|
||||
refreshRequestedAppsMutex *sync.Mutex
|
||||
metricsServer *metrics.MetricsServer
|
||||
kubectlSemaphore *semaphore.Weighted
|
||||
clusterFilter func(cluster *appv1.Cluster) bool
|
||||
clusterSharding sharding.ClusterShardingCache
|
||||
projByNameCache sync.Map
|
||||
applicationNamespaces []string
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
|
||||
|
||||
// dynamicClusterDistributionEnabled if disabled deploymentInformer is never initialized
|
||||
dynamicClusterDistributionEnabled bool
|
||||
deploymentInformer informerv1.DeploymentInformer
|
||||
}
|
||||
|
||||
// NewApplicationController creates new instance of ApplicationController.
|
||||
@@ -142,6 +148,7 @@ func NewApplicationController(
|
||||
kubectl kube.Kubectl,
|
||||
appResyncPeriod time.Duration,
|
||||
appHardResyncPeriod time.Duration,
|
||||
appResyncJitter time.Duration,
|
||||
selfHealTimeout time.Duration,
|
||||
repoErrorGracePeriod time.Duration,
|
||||
metricsPort int,
|
||||
@@ -149,39 +156,44 @@ func NewApplicationController(
|
||||
metricsApplicationLabels []string,
|
||||
kubectlParallelismLimit int64,
|
||||
persistResourceHealth bool,
|
||||
clusterFilter func(cluster *appv1.Cluster) bool,
|
||||
clusterSharding sharding.ClusterShardingCache,
|
||||
applicationNamespaces []string,
|
||||
rateLimiterConfig *ratelimiter.AppControllerRateLimiterConfig,
|
||||
serverSideDiff bool,
|
||||
dynamicClusterDistributionEnabled bool,
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts,
|
||||
) (*ApplicationController, error) {
|
||||
log.Infof("appResyncPeriod=%v, appHardResyncPeriod=%v", appResyncPeriod, appHardResyncPeriod)
|
||||
log.Infof("appResyncPeriod=%v, appHardResyncPeriod=%v, appResyncJitter=%v", appResyncPeriod, appHardResyncPeriod, appResyncJitter)
|
||||
db := db.NewDB(namespace, settingsMgr, kubeClientset)
|
||||
if rateLimiterConfig == nil {
|
||||
rateLimiterConfig = ratelimiter.GetDefaultAppRateLimiterConfig()
|
||||
log.Info("Using default workqueue rate limiter config")
|
||||
}
|
||||
ctrl := ApplicationController{
|
||||
cache: argoCache,
|
||||
namespace: namespace,
|
||||
kubeClientset: kubeClientset,
|
||||
kubectl: kubectl,
|
||||
applicationClientset: applicationClientset,
|
||||
repoClientset: repoClientset,
|
||||
appRefreshQueue: workqueue.NewNamedRateLimitingQueue(ratelimiter.NewCustomAppControllerRateLimiter(rateLimiterConfig), "app_reconciliation_queue"),
|
||||
appOperationQueue: workqueue.NewNamedRateLimitingQueue(ratelimiter.NewCustomAppControllerRateLimiter(rateLimiterConfig), "app_operation_processing_queue"),
|
||||
projectRefreshQueue: workqueue.NewNamedRateLimitingQueue(ratelimiter.NewCustomAppControllerRateLimiter(rateLimiterConfig), "project_reconciliation_queue"),
|
||||
appComparisonTypeRefreshQueue: workqueue.NewRateLimitingQueue(ratelimiter.NewCustomAppControllerRateLimiter(rateLimiterConfig)),
|
||||
db: db,
|
||||
statusRefreshTimeout: appResyncPeriod,
|
||||
statusHardRefreshTimeout: appHardResyncPeriod,
|
||||
refreshRequestedApps: make(map[string]CompareWith),
|
||||
refreshRequestedAppsMutex: &sync.Mutex{},
|
||||
auditLogger: argo.NewAuditLogger(namespace, kubeClientset, common.ApplicationController),
|
||||
settingsMgr: settingsMgr,
|
||||
selfHealTimeout: selfHealTimeout,
|
||||
clusterFilter: clusterFilter,
|
||||
projByNameCache: sync.Map{},
|
||||
applicationNamespaces: applicationNamespaces,
|
||||
cache: argoCache,
|
||||
namespace: namespace,
|
||||
kubeClientset: kubeClientset,
|
||||
kubectl: kubectl,
|
||||
applicationClientset: applicationClientset,
|
||||
repoClientset: repoClientset,
|
||||
appRefreshQueue: workqueue.NewNamedRateLimitingQueue(ratelimiter.NewCustomAppControllerRateLimiter(rateLimiterConfig), "app_reconciliation_queue"),
|
||||
appOperationQueue: workqueue.NewNamedRateLimitingQueue(ratelimiter.NewCustomAppControllerRateLimiter(rateLimiterConfig), "app_operation_processing_queue"),
|
||||
projectRefreshQueue: workqueue.NewNamedRateLimitingQueue(ratelimiter.NewCustomAppControllerRateLimiter(rateLimiterConfig), "project_reconciliation_queue"),
|
||||
appComparisonTypeRefreshQueue: workqueue.NewRateLimitingQueue(ratelimiter.NewCustomAppControllerRateLimiter(rateLimiterConfig)),
|
||||
db: db,
|
||||
statusRefreshTimeout: appResyncPeriod,
|
||||
statusHardRefreshTimeout: appHardResyncPeriod,
|
||||
statusRefreshJitter: appResyncJitter,
|
||||
refreshRequestedApps: make(map[string]CompareWith),
|
||||
refreshRequestedAppsMutex: &sync.Mutex{},
|
||||
auditLogger: argo.NewAuditLogger(namespace, kubeClientset, common.ApplicationController),
|
||||
settingsMgr: settingsMgr,
|
||||
selfHealTimeout: selfHealTimeout,
|
||||
clusterSharding: clusterSharding,
|
||||
projByNameCache: sync.Map{},
|
||||
applicationNamespaces: applicationNamespaces,
|
||||
dynamicClusterDistributionEnabled: dynamicClusterDistributionEnabled,
|
||||
ignoreNormalizerOpts: ignoreNormalizerOpts,
|
||||
}
|
||||
if kubectlParallelismLimit > 0 {
|
||||
ctrl.kubectlSemaphore = semaphore.NewWeighted(kubectlParallelismLimit)
|
||||
@@ -224,25 +236,33 @@ func NewApplicationController(
|
||||
}
|
||||
|
||||
factory := informers.NewSharedInformerFactoryWithOptions(ctrl.kubeClientset, defaultDeploymentInformerResyncDuration, informers.WithNamespace(settingsMgr.GetNamespace()))
|
||||
deploymentInformer := factory.Apps().V1().Deployments()
|
||||
|
||||
var deploymentInformer informerv1.DeploymentInformer
|
||||
|
||||
// only initialize deployment informer if dynamic distribution is enabled
|
||||
if dynamicClusterDistributionEnabled {
|
||||
deploymentInformer = factory.Apps().V1().Deployments()
|
||||
}
|
||||
|
||||
readinessHealthCheck := func(r *http.Request) error {
|
||||
applicationControllerName := env.StringFromEnv(common.EnvAppControllerName, common.DefaultApplicationControllerName)
|
||||
appControllerDeployment, err := deploymentInformer.Lister().Deployments(settingsMgr.GetNamespace()).Get(applicationControllerName)
|
||||
if err != nil {
|
||||
if kubeerrors.IsNotFound(err) {
|
||||
appControllerDeployment = nil
|
||||
} else {
|
||||
return fmt.Errorf("error retrieving Application Controller Deployment: %s", err)
|
||||
if dynamicClusterDistributionEnabled {
|
||||
applicationControllerName := env.StringFromEnv(common.EnvAppControllerName, common.DefaultApplicationControllerName)
|
||||
appControllerDeployment, err := deploymentInformer.Lister().Deployments(settingsMgr.GetNamespace()).Get(applicationControllerName)
|
||||
if err != nil {
|
||||
if kubeerrors.IsNotFound(err) {
|
||||
appControllerDeployment = nil
|
||||
} else {
|
||||
return fmt.Errorf("error retrieving Application Controller Deployment: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if appControllerDeployment != nil {
|
||||
if appControllerDeployment.Spec.Replicas != nil && int(*appControllerDeployment.Spec.Replicas) <= 0 {
|
||||
return fmt.Errorf("application controller deployment replicas is not set or is less than 0, replicas: %d", appControllerDeployment.Spec.Replicas)
|
||||
}
|
||||
shard := env.ParseNumFromEnv(common.EnvControllerShard, -1, -math.MaxInt32, math.MaxInt32)
|
||||
if _, err := sharding.GetOrUpdateShardFromConfigMap(kubeClientset.(*kubernetes.Clientset), settingsMgr, int(*appControllerDeployment.Spec.Replicas), shard); err != nil {
|
||||
return fmt.Errorf("error while updating the heartbeat for to the Shard Mapping ConfigMap: %s", err)
|
||||
if appControllerDeployment != nil {
|
||||
if appControllerDeployment.Spec.Replicas != nil && int(*appControllerDeployment.Spec.Replicas) <= 0 {
|
||||
return fmt.Errorf("application controller deployment replicas is not set or is less than 0, replicas: %d", appControllerDeployment.Spec.Replicas)
|
||||
}
|
||||
shard := env.ParseNumFromEnv(common.EnvControllerShard, -1, -math.MaxInt32, math.MaxInt32)
|
||||
if _, err := sharding.GetOrUpdateShardFromConfigMap(kubeClientset.(*kubernetes.Clientset), settingsMgr, int(*appControllerDeployment.Spec.Replicas), shard); err != nil {
|
||||
return fmt.Errorf("error while updating the heartbeat for to the Shard Mapping ConfigMap: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -260,8 +280,8 @@ func NewApplicationController(
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settingsMgr, kubectl, ctrl.metricsServer, ctrl.handleObjectUpdated, clusterFilter, argo.NewResourceTracking())
|
||||
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectl, ctrl.settingsMgr, stateCache, projInformer, ctrl.metricsServer, argoCache, ctrl.statusRefreshTimeout, argo.NewResourceTracking(), persistResourceHealth, repoErrorGracePeriod, serverSideDiff)
|
||||
stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settingsMgr, kubectl, ctrl.metricsServer, ctrl.handleObjectUpdated, clusterSharding, argo.NewResourceTracking())
|
||||
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectl, ctrl.settingsMgr, stateCache, projInformer, ctrl.metricsServer, argoCache, ctrl.statusRefreshTimeout, argo.NewResourceTracking(), persistResourceHealth, repoErrorGracePeriod, serverSideDiff, ignoreNormalizerOpts)
|
||||
ctrl.appInformer = appInformer
|
||||
ctrl.appLister = appLister
|
||||
ctrl.projInformer = projInformer
|
||||
@@ -494,13 +514,13 @@ func (ctrl *ApplicationController) getResourceTree(a *appv1.Application, managed
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal live state of managed resources: %w", err)
|
||||
}
|
||||
var target = &unstructured.Unstructured{}
|
||||
err = json.Unmarshal([]byte(managedResource.TargetState), &target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal target state of managed resources: %w", err)
|
||||
}
|
||||
|
||||
if live == nil {
|
||||
var target = &unstructured.Unstructured{}
|
||||
err = json.Unmarshal([]byte(managedResource.TargetState), &target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal target state of managed resources: %w", err)
|
||||
}
|
||||
nodes = append(nodes, appv1.ResourceNode{
|
||||
ResourceRef: appv1.ResourceRef{
|
||||
Version: target.GroupVersionKind().Version,
|
||||
@@ -712,7 +732,7 @@ func (ctrl *ApplicationController) hideSecretData(app *appv1.Application, compar
|
||||
return nil, fmt.Errorf("error getting cluster cache: %s", err)
|
||||
}
|
||||
diffConfig, err := argodiff.NewDiffConfigBuilder().
|
||||
WithDiffSettings(app.Spec.IgnoreDifferences, resourceOverrides, compareOptions.IgnoreAggregatedRoles).
|
||||
WithDiffSettings(app.Spec.IgnoreDifferences, resourceOverrides, compareOptions.IgnoreAggregatedRoles, ctrl.ignoreNormalizerOpts).
|
||||
WithTracking(appLabelKey, trackingMethod).
|
||||
WithNoCache().
|
||||
WithLogger(logutils.NewLogrusLogger(logutils.NewWithCurrentConfig())).
|
||||
@@ -770,7 +790,24 @@ func (ctrl *ApplicationController) Run(ctx context.Context, statusProcessors int
|
||||
|
||||
go ctrl.appInformer.Run(ctx.Done())
|
||||
go ctrl.projInformer.Run(ctx.Done())
|
||||
go ctrl.deploymentInformer.Informer().Run(ctx.Done())
|
||||
|
||||
if ctrl.dynamicClusterDistributionEnabled {
|
||||
// only start deployment informer if dynamic distribution is enabled
|
||||
go ctrl.deploymentInformer.Informer().Run(ctx.Done())
|
||||
}
|
||||
|
||||
clusters, err := ctrl.db.ListClusters(ctx)
|
||||
if err != nil {
|
||||
log.Warnf("Cannot init sharding. Error while querying clusters list from database: %v", err)
|
||||
} else {
|
||||
appItems, err := ctrl.getAppList(metav1.ListOptions{})
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Cannot init sharding. Error while querying application list from database: %v", err)
|
||||
} else {
|
||||
ctrl.clusterSharding.Init(clusters, appItems)
|
||||
}
|
||||
}
|
||||
|
||||
errors.CheckError(ctrl.stateCache.Init())
|
||||
|
||||
@@ -1023,7 +1060,7 @@ func (ctrl *ApplicationController) getPermittedAppLiveObjects(app *appv1.Applica
|
||||
return objsMap, nil
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) isValidDestination(app *appv1.Application) (bool, *argov1alpha.Cluster) {
|
||||
func (ctrl *ApplicationController) isValidDestination(app *appv1.Application) (bool, *appv1.Cluster) {
|
||||
// Validate the cluster using the Application destination's `name` field, if applicable,
|
||||
// and set the Server field, if needed.
|
||||
if err := argo.ValidateDestination(context.Background(), &app.Spec.Destination, ctrl.db); err != nil {
|
||||
@@ -1636,6 +1673,7 @@ func (ctrl *ApplicationController) needRefreshAppStatus(app *appv1.Application,
|
||||
var reason string
|
||||
compareWith := CompareWithLatest
|
||||
refreshType := appv1.RefreshTypeNormal
|
||||
|
||||
softExpired := app.Status.ReconciledAt == nil || app.Status.ReconciledAt.Add(statusRefreshTimeout).Before(time.Now().UTC())
|
||||
hardExpired := (app.Status.ReconciledAt == nil || app.Status.ReconciledAt.Add(statusHardRefreshTimeout).Before(time.Now().UTC())) && statusHardRefreshTimeout.Seconds() != 0
|
||||
|
||||
@@ -1885,7 +1923,15 @@ func (ctrl *ApplicationController) autoSync(app *appv1.Application, syncStatus *
|
||||
} else {
|
||||
ctrl.writeBackToInformer(updatedApp)
|
||||
}
|
||||
message := fmt.Sprintf("Initiated automated sync to '%s'", desiredCommitSHA)
|
||||
|
||||
var target string
|
||||
if updatedApp.Spec.HasMultipleSources() {
|
||||
target = strings.Join(desiredCommitSHAsMS, ", ")
|
||||
} else {
|
||||
target = desiredCommitSHA
|
||||
}
|
||||
message := fmt.Sprintf("Initiated automated sync to '%s'", target)
|
||||
|
||||
ctrl.auditLogger.LogAppEvent(app, argo.EventInfo{Reason: argo.EventReasonOperationStarted, Type: v1.EventTypeNormal}, message, "")
|
||||
logCtx.Info(message)
|
||||
return nil, setOpTime
|
||||
@@ -1976,15 +2022,11 @@ func (ctrl *ApplicationController) canProcessApp(obj interface{}) bool {
|
||||
}
|
||||
}
|
||||
|
||||
if ctrl.clusterFilter != nil {
|
||||
cluster, err := ctrl.db.GetCluster(context.Background(), app.Spec.Destination.Server)
|
||||
if err != nil {
|
||||
return ctrl.clusterFilter(nil)
|
||||
}
|
||||
return ctrl.clusterFilter(cluster)
|
||||
cluster, err := ctrl.db.GetCluster(context.Background(), app.Spec.Destination.Server)
|
||||
if err != nil {
|
||||
return ctrl.clusterSharding.IsManagedCluster(nil)
|
||||
}
|
||||
|
||||
return true
|
||||
return ctrl.clusterSharding.IsManagedCluster(cluster)
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.SharedIndexInformer, applisters.ApplicationLister) {
|
||||
@@ -2082,6 +2124,10 @@ func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.Shar
|
||||
ctrl.appRefreshQueue.AddRateLimited(key)
|
||||
ctrl.appOperationQueue.AddRateLimited(key)
|
||||
}
|
||||
newApp, newOK := obj.(*appv1.Application)
|
||||
if err == nil && newOK {
|
||||
ctrl.clusterSharding.AddApp(newApp)
|
||||
}
|
||||
},
|
||||
UpdateFunc: func(old, new interface{}) {
|
||||
if !ctrl.canProcessApp(new) {
|
||||
@@ -2092,15 +2138,27 @@ func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.Shar
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var compareWith *CompareWith
|
||||
var delay *time.Duration
|
||||
|
||||
oldApp, oldOK := old.(*appv1.Application)
|
||||
newApp, newOK := new.(*appv1.Application)
|
||||
if oldOK && newOK && automatedSyncEnabled(oldApp, newApp) {
|
||||
log.WithField("application", newApp.QualifiedName()).Info("Enabled automated sync")
|
||||
compareWith = CompareWithLatest.Pointer()
|
||||
if oldOK && newOK {
|
||||
if automatedSyncEnabled(oldApp, newApp) {
|
||||
log.WithField("application", newApp.QualifiedName()).Info("Enabled automated sync")
|
||||
compareWith = CompareWithLatest.Pointer()
|
||||
}
|
||||
if ctrl.statusRefreshJitter != 0 && oldApp.ResourceVersion == newApp.ResourceVersion {
|
||||
// Handler is refreshing the apps, add a random jitter to spread the load and avoid spikes
|
||||
jitter := time.Duration(float64(ctrl.statusRefreshJitter) * rand.Float64())
|
||||
delay = &jitter
|
||||
}
|
||||
}
|
||||
ctrl.requestAppRefresh(newApp.QualifiedName(), compareWith, nil)
|
||||
|
||||
ctrl.requestAppRefresh(newApp.QualifiedName(), compareWith, delay)
|
||||
ctrl.appOperationQueue.AddRateLimited(key)
|
||||
ctrl.clusterSharding.UpdateApp(newApp)
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
if !ctrl.canProcessApp(obj) {
|
||||
@@ -2113,6 +2171,10 @@ func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.Shar
|
||||
// for deletes, we immediately add to the refresh queue
|
||||
ctrl.appRefreshQueue.Add(key)
|
||||
}
|
||||
delApp, delOK := obj.(*appv1.Application)
|
||||
if err == nil && delOK {
|
||||
ctrl.clusterSharding.DeleteApp(delApp)
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -2136,7 +2198,7 @@ func (ctrl *ApplicationController) projectErrorToCondition(err error, app *appv1
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) RegisterClusterSecretUpdater(ctx context.Context) {
|
||||
updater := NewClusterInfoUpdater(ctrl.stateCache, ctrl.db, ctrl.appLister.Applications(""), ctrl.cache, ctrl.clusterFilter, ctrl.getAppProj, ctrl.namespace)
|
||||
updater := NewClusterInfoUpdater(ctrl.stateCache, ctrl.db, ctrl.appLister.Applications(""), ctrl.cache, ctrl.clusterSharding.IsManagedCluster, ctrl.getAppProj, ctrl.namespace)
|
||||
go updater.Run(ctx)
|
||||
}
|
||||
|
||||
@@ -2188,4 +2250,26 @@ func (ctrl *ApplicationController) toAppQualifiedName(appName, appNamespace stri
|
||||
return fmt.Sprintf("%s/%s", appNamespace, appName)
|
||||
}
|
||||
|
||||
type ClusterFilterFunction func(c *argov1alpha.Cluster, distributionFunction sharding.DistributionFunction) bool
|
||||
func (ctrl *ApplicationController) getAppList(options metav1.ListOptions) (*appv1.ApplicationList, error) {
|
||||
watchNamespace := ctrl.namespace
|
||||
// If we have at least one additional namespace configured, we need to
|
||||
// watch on them all.
|
||||
if len(ctrl.applicationNamespaces) > 0 {
|
||||
watchNamespace = ""
|
||||
}
|
||||
|
||||
appList, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(watchNamespace).List(context.TODO(), options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newItems := []appv1.Application{}
|
||||
for _, app := range appList.Items {
|
||||
if ctrl.isAppNamespaceAllowed(&app) {
|
||||
newItems = append(newItems, app)
|
||||
}
|
||||
}
|
||||
appList.Items = newItems
|
||||
return appList, nil
|
||||
}
|
||||
|
||||
type ClusterFilterFunction func(c *appv1.Cluster, distributionFunction sharding.DistributionFunction) bool
|
||||
|
||||
@@ -17,7 +17,9 @@ import (
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
statecache "github.com/argoproj/argo-cd/v2/controller/cache"
|
||||
"github.com/argoproj/argo-cd/v2/controller/sharding"
|
||||
|
||||
dbmocks "github.com/argoproj/argo-cd/v2/util/db/mocks"
|
||||
"github.com/argoproj/gitops-engine/pkg/cache/mocks"
|
||||
synccommon "github.com/argoproj/gitops-engine/pkg/sync/common"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
@@ -40,6 +42,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
mockrepoclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
|
||||
"github.com/argoproj/argo-cd/v2/test"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
|
||||
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
|
||||
"github.com/argoproj/argo-cd/v2/util/settings"
|
||||
@@ -51,14 +54,15 @@ type namespacedResource struct {
|
||||
}
|
||||
|
||||
type fakeData struct {
|
||||
apps []runtime.Object
|
||||
manifestResponse *apiclient.ManifestResponse
|
||||
manifestResponses []*apiclient.ManifestResponse
|
||||
managedLiveObjs map[kube.ResourceKey]*unstructured.Unstructured
|
||||
namespacedResources map[kube.ResourceKey]namespacedResource
|
||||
configMapData map[string]string
|
||||
metricsCacheExpiration time.Duration
|
||||
applicationNamespaces []string
|
||||
apps []runtime.Object
|
||||
manifestResponse *apiclient.ManifestResponse
|
||||
manifestResponses []*apiclient.ManifestResponse
|
||||
managedLiveObjs map[kube.ResourceKey]*unstructured.Unstructured
|
||||
namespacedResources map[kube.ResourceKey]namespacedResource
|
||||
configMapData map[string]string
|
||||
metricsCacheExpiration time.Duration
|
||||
applicationNamespaces []string
|
||||
updateRevisionForPathsResponse *apiclient.UpdateRevisionForPathsResponse
|
||||
}
|
||||
|
||||
type MockKubectl struct {
|
||||
@@ -104,6 +108,8 @@ func newFakeController(data *fakeData, repoErr error) *ApplicationController {
|
||||
}
|
||||
}
|
||||
|
||||
mockRepoClient.On("UpdateRevisionForPaths", mock.Anything, mock.Anything).Return(data.updateRevisionForPathsResponse, nil)
|
||||
|
||||
mockRepoClientset := mockrepoclient.Clientset{RepoServerServiceClient: &mockRepoClient}
|
||||
|
||||
secret := corev1.Secret{
|
||||
@@ -142,6 +148,7 @@ func newFakeController(data *fakeData, repoErr error) *ApplicationController {
|
||||
kubectl,
|
||||
time.Minute,
|
||||
time.Hour,
|
||||
time.Second,
|
||||
time.Minute,
|
||||
time.Second*10,
|
||||
common.DefaultPortArgoCDMetrics,
|
||||
@@ -153,7 +160,13 @@ func newFakeController(data *fakeData, repoErr error) *ApplicationController {
|
||||
data.applicationNamespaces,
|
||||
nil,
|
||||
false,
|
||||
false,
|
||||
normalizers.IgnoreNormalizerOpts{},
|
||||
)
|
||||
db := &dbmocks.ArgoDB{}
|
||||
db.On("GetApplicationControllerReplicas").Return(1)
|
||||
// Setting a default sharding algorithm for the tests where we cannot set it.
|
||||
ctrl.clusterSharding = sharding.NewClusterSharding(db, 0, 1, common.DefaultShardingAlgorithm)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -686,7 +699,6 @@ func TestFinalizeAppDeletion(t *testing.T) {
|
||||
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app, &defaultProj}, managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
|
||||
kube.GetResourceKey(appObj): appObj,
|
||||
}}, nil)
|
||||
|
||||
patched := false
|
||||
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
|
||||
defaultReactor := fakeAppCs.ReactionChain[0]
|
||||
@@ -977,7 +989,7 @@ func TestNormalizeApplication(t *testing.T) {
|
||||
normalized := false
|
||||
fakeAppCs.AddReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
if patchAction, ok := action.(kubetesting.PatchAction); ok {
|
||||
if string(patchAction.GetPatch()) == `{"spec":{"project":"default"}}` {
|
||||
if string(patchAction.GetPatch()) == `{"spec":{"project":"default"},"status":{"sync":{"comparedTo":{"destination":{},"source":{"repoURL":""}}}}}` {
|
||||
normalized = true
|
||||
}
|
||||
}
|
||||
@@ -999,7 +1011,7 @@ func TestNormalizeApplication(t *testing.T) {
|
||||
normalized := false
|
||||
fakeAppCs.AddReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
if patchAction, ok := action.(kubetesting.PatchAction); ok {
|
||||
if string(patchAction.GetPatch()) == `{"spec":{"project":"default"}}` {
|
||||
if string(patchAction.GetPatch()) == `{"spec":{"project":"default"},"status":{"sync":{"comparedTo":{"destination":{},"source":{"repoURL":""}}}}}` {
|
||||
normalized = true
|
||||
}
|
||||
}
|
||||
@@ -1809,13 +1821,11 @@ func Test_canProcessApp(t *testing.T) {
|
||||
})
|
||||
t.Run("with cluster filter, good namespace", func(t *testing.T) {
|
||||
app.Namespace = "good"
|
||||
ctrl.clusterFilter = func(_ *v1alpha1.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(_ *v1alpha1.Cluster) bool { return true }
|
||||
canProcess := ctrl.canProcessApp(app)
|
||||
assert.False(t, canProcess)
|
||||
})
|
||||
|
||||
69
controller/cache/cache.go
vendored
@@ -29,9 +29,11 @@ import (
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/controller/metrics"
|
||||
"github.com/argoproj/argo-cd/v2/controller/sharding"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
|
||||
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
"github.com/argoproj/argo-cd/v2/util/db"
|
||||
"github.com/argoproj/argo-cd/v2/util/env"
|
||||
logutils "github.com/argoproj/argo-cd/v2/util/log"
|
||||
@@ -168,7 +170,7 @@ func NewLiveStateCache(
|
||||
kubectl kube.Kubectl,
|
||||
metricsServer *metrics.MetricsServer,
|
||||
onObjectUpdated ObjectUpdatedHandler,
|
||||
clusterFilter func(cluster *appv1.Cluster) bool,
|
||||
clusterSharding sharding.ClusterShardingCache,
|
||||
resourceTracking argo.ResourceTracking) LiveStateCache {
|
||||
|
||||
return &liveStateCache{
|
||||
@@ -179,7 +181,7 @@ func NewLiveStateCache(
|
||||
kubectl: kubectl,
|
||||
settingsMgr: settingsMgr,
|
||||
metricsServer: metricsServer,
|
||||
clusterFilter: clusterFilter,
|
||||
clusterSharding: clusterSharding,
|
||||
resourceTracking: resourceTracking,
|
||||
}
|
||||
}
|
||||
@@ -196,14 +198,15 @@ type cacheSettings struct {
|
||||
}
|
||||
|
||||
type liveStateCache struct {
|
||||
db db.ArgoDB
|
||||
appInformer cache.SharedIndexInformer
|
||||
onObjectUpdated ObjectUpdatedHandler
|
||||
kubectl kube.Kubectl
|
||||
settingsMgr *settings.SettingsManager
|
||||
metricsServer *metrics.MetricsServer
|
||||
clusterFilter func(cluster *appv1.Cluster) bool
|
||||
resourceTracking argo.ResourceTracking
|
||||
db db.ArgoDB
|
||||
appInformer cache.SharedIndexInformer
|
||||
onObjectUpdated ObjectUpdatedHandler
|
||||
kubectl kube.Kubectl
|
||||
settingsMgr *settings.SettingsManager
|
||||
metricsServer *metrics.MetricsServer
|
||||
clusterSharding sharding.ClusterShardingCache
|
||||
resourceTracking argo.ResourceTracking
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
|
||||
|
||||
clusters map[string]clustercache.ClusterCache
|
||||
cacheSettings cacheSettings
|
||||
@@ -287,7 +290,8 @@ func isRootAppNode(r *clustercache.Resource) bool {
|
||||
}
|
||||
|
||||
func getApp(r *clustercache.Resource, ns map[kube.ResourceKey]*clustercache.Resource) string {
|
||||
return getAppRecursive(r, ns, map[kube.ResourceKey]bool{})
|
||||
name, _ := getAppRecursive(r, ns, map[kube.ResourceKey]bool{})
|
||||
return name
|
||||
}
|
||||
|
||||
func ownerRefGV(ownerRef metav1.OwnerReference) schema.GroupVersion {
|
||||
@@ -298,27 +302,31 @@ func ownerRefGV(ownerRef metav1.OwnerReference) schema.GroupVersion {
|
||||
return gv
|
||||
}
|
||||
|
||||
func getAppRecursive(r *clustercache.Resource, ns map[kube.ResourceKey]*clustercache.Resource, visited map[kube.ResourceKey]bool) string {
|
||||
func getAppRecursive(r *clustercache.Resource, ns map[kube.ResourceKey]*clustercache.Resource, visited map[kube.ResourceKey]bool) (string, bool) {
|
||||
if !visited[r.ResourceKey()] {
|
||||
visited[r.ResourceKey()] = true
|
||||
} else {
|
||||
log.Warnf("Circular dependency detected: %v.", visited)
|
||||
return resInfo(r).AppName
|
||||
return resInfo(r).AppName, false
|
||||
}
|
||||
|
||||
if resInfo(r).AppName != "" {
|
||||
return resInfo(r).AppName
|
||||
return resInfo(r).AppName, true
|
||||
}
|
||||
for _, ownerRef := range r.OwnerRefs {
|
||||
gv := ownerRefGV(ownerRef)
|
||||
if parent, ok := ns[kube.NewResourceKey(gv.Group, ownerRef.Kind, r.Ref.Namespace, ownerRef.Name)]; ok {
|
||||
app := getAppRecursive(parent, ns, visited)
|
||||
if app != "" {
|
||||
return app
|
||||
visited_branch := make(map[kube.ResourceKey]bool, len(visited))
|
||||
for k, v := range visited {
|
||||
visited_branch[k] = v
|
||||
}
|
||||
app, ok := getAppRecursive(parent, ns, visited_branch)
|
||||
if app != "" || !ok {
|
||||
return app, ok
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return "", true
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -371,9 +379,14 @@ func isRetryableError(err error) bool {
|
||||
isResourceQuotaConflictErr(err) ||
|
||||
isTransientNetworkErr(err) ||
|
||||
isExceededQuotaErr(err) ||
|
||||
isHTTP2GoawayErr(err) ||
|
||||
errors.Is(err, syscall.ECONNRESET)
|
||||
}
|
||||
|
||||
func isHTTP2GoawayErr(err error) bool {
|
||||
return strings.Contains(err.Error(), "http2: server sent GOAWAY and closed the connection")
|
||||
}
|
||||
|
||||
func isExceededQuotaErr(err error) bool {
|
||||
return kerrors.IsForbidden(err) && strings.Contains(err.Error(), "exceeded quota")
|
||||
}
|
||||
@@ -431,6 +444,10 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
|
||||
return nil, fmt.Errorf("error getting cluster: %w", err)
|
||||
}
|
||||
|
||||
if c.clusterSharding == nil {
|
||||
return nil, fmt.Errorf("unable to handle cluster %s: cluster sharding is not configured", cluster.Server)
|
||||
}
|
||||
|
||||
if !c.canHandleCluster(cluster) {
|
||||
return nil, fmt.Errorf("controller is configured to ignore cluster %s", cluster.Server)
|
||||
}
|
||||
@@ -486,7 +503,7 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
|
||||
gvk := un.GroupVersionKind()
|
||||
|
||||
if cacheSettings.ignoreResourceUpdatesEnabled && shouldHashManifest(appName, gvk) {
|
||||
hash, err := generateManifestHash(un, nil, cacheSettings.resourceOverrides)
|
||||
hash, err := generateManifestHash(un, nil, cacheSettings.resourceOverrides, c.ignoreNormalizerOpts)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to generate manifest hash: %v", err)
|
||||
} else {
|
||||
@@ -722,22 +739,24 @@ func (c *liveStateCache) Run(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (c *liveStateCache) canHandleCluster(cluster *appv1.Cluster) bool {
|
||||
if c.clusterFilter == nil {
|
||||
return true
|
||||
}
|
||||
return c.clusterFilter(cluster)
|
||||
return c.clusterSharding.IsManagedCluster(cluster)
|
||||
}
|
||||
|
||||
func (c *liveStateCache) handleAddEvent(cluster *appv1.Cluster) {
|
||||
c.clusterSharding.Add(cluster)
|
||||
if !c.canHandleCluster(cluster) {
|
||||
log.Infof("Ignoring cluster %s", cluster.Server)
|
||||
return
|
||||
}
|
||||
|
||||
c.lock.Lock()
|
||||
_, ok := c.clusters[cluster.Server]
|
||||
c.lock.Unlock()
|
||||
if !ok {
|
||||
log.Debugf("Checking if cache %v / cluster %v has appInformer %v", c, cluster, c.appInformer)
|
||||
if c.appInformer == nil {
|
||||
log.Warn("Cannot get a cluster appInformer. Cache may not be started this time")
|
||||
return
|
||||
}
|
||||
if c.isClusterHasApps(c.appInformer.GetStore().List(), cluster) {
|
||||
go func() {
|
||||
// warm up cache for cluster with apps
|
||||
@@ -748,6 +767,7 @@ func (c *liveStateCache) handleAddEvent(cluster *appv1.Cluster) {
|
||||
}
|
||||
|
||||
func (c *liveStateCache) handleModEvent(oldCluster *appv1.Cluster, newCluster *appv1.Cluster) {
|
||||
c.clusterSharding.Update(oldCluster, newCluster)
|
||||
c.lock.Lock()
|
||||
cluster, ok := c.clusters[newCluster.Server]
|
||||
c.lock.Unlock()
|
||||
@@ -790,6 +810,7 @@ func (c *liveStateCache) handleModEvent(oldCluster *appv1.Cluster, newCluster *a
|
||||
|
||||
func (c *liveStateCache) handleDeleteEvent(clusterServer string) {
|
||||
c.lock.RLock()
|
||||
c.clusterSharding.Delete(clusterServer)
|
||||
cluster, ok := c.clusters[clusterServer]
|
||||
c.lock.RUnlock()
|
||||
if ok {
|
||||
|
||||
261
controller/cache/cache_test.go
vendored
@@ -18,10 +18,15 @@ import (
|
||||
"github.com/argoproj/gitops-engine/pkg/cache"
|
||||
"github.com/argoproj/gitops-engine/pkg/cache/mocks"
|
||||
"github.com/argoproj/gitops-engine/pkg/health"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/controller/metrics"
|
||||
"github.com/argoproj/argo-cd/v2/controller/sharding"
|
||||
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
dbmocks "github.com/argoproj/argo-cd/v2/util/db/mocks"
|
||||
argosettings "github.com/argoproj/argo-cd/v2/util/settings"
|
||||
)
|
||||
|
||||
@@ -35,11 +40,13 @@ func TestHandleModEvent_HasChanges(t *testing.T) {
|
||||
clusterCache := &mocks.ClusterCache{}
|
||||
clusterCache.On("Invalidate", mock.Anything, mock.Anything).Return(nil).Once()
|
||||
clusterCache.On("EnsureSynced").Return(nil).Once()
|
||||
|
||||
db := &dbmocks.ArgoDB{}
|
||||
db.On("GetApplicationControllerReplicas").Return(1)
|
||||
clustersCache := liveStateCache{
|
||||
clusters: map[string]cache.ClusterCache{
|
||||
"https://mycluster": clusterCache,
|
||||
},
|
||||
clusterSharding: sharding.NewClusterSharding(db, 0, 1, common.DefaultShardingAlgorithm),
|
||||
}
|
||||
|
||||
clustersCache.handleModEvent(&appv1.Cluster{
|
||||
@@ -56,14 +63,22 @@ func TestHandleModEvent_ClusterExcluded(t *testing.T) {
|
||||
clusterCache := &mocks.ClusterCache{}
|
||||
clusterCache.On("Invalidate", mock.Anything, mock.Anything).Return(nil).Once()
|
||||
clusterCache.On("EnsureSynced").Return(nil).Once()
|
||||
|
||||
db := &dbmocks.ArgoDB{}
|
||||
db.On("GetApplicationControllerReplicas").Return(1)
|
||||
clustersCache := liveStateCache{
|
||||
clusters: map[string]cache.ClusterCache{
|
||||
"https://mycluster": clusterCache,
|
||||
},
|
||||
clusterFilter: func(cluster *appv1.Cluster) bool {
|
||||
return false
|
||||
db: nil,
|
||||
appInformer: nil,
|
||||
onObjectUpdated: func(managedByApp map[string]bool, ref v1.ObjectReference) {
|
||||
},
|
||||
kubectl: nil,
|
||||
settingsMgr: &argosettings.SettingsManager{},
|
||||
metricsServer: &metrics.MetricsServer{},
|
||||
// returns a shard that never process any cluster
|
||||
clusterSharding: sharding.NewClusterSharding(db, 0, 1, common.DefaultShardingAlgorithm),
|
||||
resourceTracking: nil,
|
||||
clusters: map[string]cache.ClusterCache{"https://mycluster": clusterCache},
|
||||
cacheSettings: cacheSettings{},
|
||||
lock: sync.RWMutex{},
|
||||
}
|
||||
|
||||
clustersCache.handleModEvent(&appv1.Cluster{
|
||||
@@ -75,18 +90,20 @@ func TestHandleModEvent_ClusterExcluded(t *testing.T) {
|
||||
Namespaces: []string{"default"},
|
||||
})
|
||||
|
||||
assert.Len(t, clustersCache.clusters, 0)
|
||||
assert.Len(t, clustersCache.clusters, 1)
|
||||
}
|
||||
|
||||
func TestHandleModEvent_NoChanges(t *testing.T) {
|
||||
clusterCache := &mocks.ClusterCache{}
|
||||
clusterCache.On("Invalidate", mock.Anything).Panic("should not invalidate")
|
||||
clusterCache.On("EnsureSynced").Return(nil).Panic("should not re-sync")
|
||||
|
||||
db := &dbmocks.ArgoDB{}
|
||||
db.On("GetApplicationControllerReplicas").Return(1)
|
||||
clustersCache := liveStateCache{
|
||||
clusters: map[string]cache.ClusterCache{
|
||||
"https://mycluster": clusterCache,
|
||||
},
|
||||
clusterSharding: sharding.NewClusterSharding(db, 0, 1, common.DefaultShardingAlgorithm),
|
||||
}
|
||||
|
||||
clustersCache.handleModEvent(&appv1.Cluster{
|
||||
@@ -99,11 +116,11 @@ func TestHandleModEvent_NoChanges(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHandleAddEvent_ClusterExcluded(t *testing.T) {
|
||||
db := &dbmocks.ArgoDB{}
|
||||
db.On("GetApplicationControllerReplicas").Return(1)
|
||||
clustersCache := liveStateCache{
|
||||
clusters: map[string]cache.ClusterCache{},
|
||||
clusterFilter: func(cluster *appv1.Cluster) bool {
|
||||
return false
|
||||
},
|
||||
clusters: map[string]cache.ClusterCache{},
|
||||
clusterSharding: sharding.NewClusterSharding(db, 0, 2, common.DefaultShardingAlgorithm),
|
||||
}
|
||||
clustersCache.handleAddEvent(&appv1.Cluster{
|
||||
Server: "https://mycluster",
|
||||
@@ -118,6 +135,8 @@ func TestHandleDeleteEvent_CacheDeadlock(t *testing.T) {
|
||||
Server: "https://mycluster",
|
||||
Config: appv1.ClusterConfig{Username: "bar"},
|
||||
}
|
||||
db := &dbmocks.ArgoDB{}
|
||||
db.On("GetApplicationControllerReplicas").Return(1)
|
||||
fakeClient := fake.NewSimpleClientset()
|
||||
settingsMgr := argosettings.NewSettingsManager(context.TODO(), fakeClient, "argocd")
|
||||
liveStateCacheLock := sync.RWMutex{}
|
||||
@@ -126,10 +145,8 @@ func TestHandleDeleteEvent_CacheDeadlock(t *testing.T) {
|
||||
clusters: map[string]cache.ClusterCache{
|
||||
testCluster.Server: gitopsEngineClusterCache,
|
||||
},
|
||||
clusterFilter: func(cluster *appv1.Cluster) bool {
|
||||
return true
|
||||
},
|
||||
settingsMgr: settingsMgr,
|
||||
clusterSharding: sharding.NewClusterSharding(db, 0, 1, common.DefaultShardingAlgorithm),
|
||||
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: liveStateCacheLock,
|
||||
@@ -303,6 +320,216 @@ func Test_asResourceNode_owner_refs(t *testing.T) {
|
||||
assert.Equal(t, expected, resNode)
|
||||
}
|
||||
|
||||
func Test_getAppRecursive(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
r *cache.Resource
|
||||
ns map[kube.ResourceKey]*cache.Resource
|
||||
wantName string
|
||||
wantOK assert.BoolAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "ok: cm1->app1",
|
||||
r: &cache.Resource{
|
||||
Ref: v1.ObjectReference{
|
||||
Name: "cm1",
|
||||
},
|
||||
OwnerRefs: []metav1.OwnerReference{
|
||||
{Name: "app1"},
|
||||
},
|
||||
},
|
||||
ns: map[kube.ResourceKey]*cache.Resource{
|
||||
kube.NewResourceKey("", "", "", "app1"): {
|
||||
Info: &ResourceInfo{
|
||||
AppName: "app1",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantName: "app1",
|
||||
wantOK: assert.True,
|
||||
},
|
||||
{
|
||||
name: "ok: cm1->cm2->app1",
|
||||
r: &cache.Resource{
|
||||
Ref: v1.ObjectReference{
|
||||
Name: "cm1",
|
||||
},
|
||||
OwnerRefs: []metav1.OwnerReference{
|
||||
{Name: "cm2"},
|
||||
},
|
||||
},
|
||||
ns: map[kube.ResourceKey]*cache.Resource{
|
||||
kube.NewResourceKey("", "", "", "cm2"): {
|
||||
Ref: v1.ObjectReference{
|
||||
Name: "cm2",
|
||||
},
|
||||
OwnerRefs: []metav1.OwnerReference{
|
||||
{Name: "app1"},
|
||||
},
|
||||
},
|
||||
kube.NewResourceKey("", "", "", "app1"): {
|
||||
Info: &ResourceInfo{
|
||||
AppName: "app1",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantName: "app1",
|
||||
wantOK: assert.True,
|
||||
},
|
||||
{
|
||||
name: "cm1->cm2->app1 & cm1->cm3->app1",
|
||||
r: &cache.Resource{
|
||||
Ref: v1.ObjectReference{
|
||||
Name: "cm1",
|
||||
},
|
||||
OwnerRefs: []metav1.OwnerReference{
|
||||
{Name: "cm2"},
|
||||
{Name: "cm3"},
|
||||
},
|
||||
},
|
||||
ns: map[kube.ResourceKey]*cache.Resource{
|
||||
kube.NewResourceKey("", "", "", "cm2"): {
|
||||
Ref: v1.ObjectReference{
|
||||
Name: "cm2",
|
||||
},
|
||||
OwnerRefs: []metav1.OwnerReference{
|
||||
{Name: "app1"},
|
||||
},
|
||||
},
|
||||
kube.NewResourceKey("", "", "", "cm3"): {
|
||||
Ref: v1.ObjectReference{
|
||||
Name: "cm3",
|
||||
},
|
||||
OwnerRefs: []metav1.OwnerReference{
|
||||
{Name: "app1"},
|
||||
},
|
||||
},
|
||||
kube.NewResourceKey("", "", "", "app1"): {
|
||||
Info: &ResourceInfo{
|
||||
AppName: "app1",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantName: "app1",
|
||||
wantOK: assert.True,
|
||||
},
|
||||
{
|
||||
// Nothing cycle.
|
||||
// Issue #11699, fixed #12667.
|
||||
name: "ok: cm1->cm2 & cm1->cm3->cm2 & cm1->cm3->app1",
|
||||
r: &cache.Resource{
|
||||
Ref: v1.ObjectReference{
|
||||
Name: "cm1",
|
||||
},
|
||||
OwnerRefs: []metav1.OwnerReference{
|
||||
{Name: "cm2"},
|
||||
{Name: "cm3"},
|
||||
},
|
||||
},
|
||||
ns: map[kube.ResourceKey]*cache.Resource{
|
||||
kube.NewResourceKey("", "", "", "cm2"): {
|
||||
Ref: v1.ObjectReference{
|
||||
Name: "cm2",
|
||||
},
|
||||
},
|
||||
kube.NewResourceKey("", "", "", "cm3"): {
|
||||
Ref: v1.ObjectReference{
|
||||
Name: "cm3",
|
||||
},
|
||||
OwnerRefs: []metav1.OwnerReference{
|
||||
{Name: "cm2"},
|
||||
{Name: "app1"},
|
||||
},
|
||||
},
|
||||
kube.NewResourceKey("", "", "", "app1"): {
|
||||
Info: &ResourceInfo{
|
||||
AppName: "app1",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantName: "app1",
|
||||
wantOK: assert.True,
|
||||
},
|
||||
{
|
||||
name: "cycle: cm1<->cm2",
|
||||
r: &cache.Resource{
|
||||
Ref: v1.ObjectReference{
|
||||
Name: "cm1",
|
||||
},
|
||||
OwnerRefs: []metav1.OwnerReference{
|
||||
{Name: "cm2"},
|
||||
},
|
||||
},
|
||||
ns: map[kube.ResourceKey]*cache.Resource{
|
||||
kube.NewResourceKey("", "", "", "cm1"): {
|
||||
Ref: v1.ObjectReference{
|
||||
Name: "cm1",
|
||||
},
|
||||
OwnerRefs: []metav1.OwnerReference{
|
||||
{Name: "cm2"},
|
||||
},
|
||||
},
|
||||
kube.NewResourceKey("", "", "", "cm2"): {
|
||||
Ref: v1.ObjectReference{
|
||||
Name: "cm2",
|
||||
},
|
||||
OwnerRefs: []metav1.OwnerReference{
|
||||
{Name: "cm1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantName: "",
|
||||
wantOK: assert.False,
|
||||
},
|
||||
{
|
||||
name: "cycle: cm1->cm2->cm3->cm1",
|
||||
r: &cache.Resource{
|
||||
Ref: v1.ObjectReference{
|
||||
Name: "cm1",
|
||||
},
|
||||
OwnerRefs: []metav1.OwnerReference{
|
||||
{Name: "cm2"},
|
||||
},
|
||||
},
|
||||
ns: map[kube.ResourceKey]*cache.Resource{
|
||||
kube.NewResourceKey("", "", "", "cm1"): {
|
||||
Ref: v1.ObjectReference{
|
||||
Name: "cm1",
|
||||
},
|
||||
OwnerRefs: []metav1.OwnerReference{
|
||||
{Name: "cm2"},
|
||||
},
|
||||
},
|
||||
kube.NewResourceKey("", "", "", "cm2"): {
|
||||
Ref: v1.ObjectReference{
|
||||
Name: "cm2",
|
||||
},
|
||||
OwnerRefs: []metav1.OwnerReference{
|
||||
{Name: "cm3"},
|
||||
},
|
||||
},
|
||||
kube.NewResourceKey("", "", "", "cm3"): {
|
||||
Ref: v1.ObjectReference{
|
||||
Name: "cm3",
|
||||
},
|
||||
OwnerRefs: []metav1.OwnerReference{
|
||||
{Name: "cm1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantName: "",
|
||||
wantOK: assert.False,
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
visited := map[kube.ResourceKey]bool{}
|
||||
got, ok := getAppRecursive(tt.r, tt.ns, visited)
|
||||
assert.Equal(t, tt.wantName, got)
|
||||
tt.wantOK(t, ok)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkipResourceUpdate(t *testing.T) {
|
||||
var (
|
||||
hash1_x string = "x"
|
||||
|
||||
4
controller/cache/info.go
vendored
@@ -408,8 +408,8 @@ func populateHostNodeInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
func generateManifestHash(un *unstructured.Unstructured, ignores []v1alpha1.ResourceIgnoreDifferences, overrides map[string]v1alpha1.ResourceOverride) (string, error) {
|
||||
normalizer, err := normalizers.NewIgnoreNormalizer(ignores, overrides)
|
||||
func generateManifestHash(un *unstructured.Unstructured, ignores []v1alpha1.ResourceIgnoreDifferences, overrides map[string]v1alpha1.ResourceOverride, opts normalizers.IgnoreNormalizerOpts) (string, error) {
|
||||
normalizer, err := normalizers.NewIgnoreNormalizer(ignores, overrides, opts)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error creating normalizer: %w", err)
|
||||
}
|
||||
|
||||
3
controller/cache/info_test.go
vendored
@@ -16,6 +16,7 @@ import (
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
)
|
||||
|
||||
func strToUnstructured(jsonStr string) *unstructured.Unstructured {
|
||||
@@ -749,7 +750,7 @@ func TestManifestHash(t *testing.T) {
|
||||
|
||||
expected := hash(data)
|
||||
|
||||
hash, err := generateManifestHash(manifest, ignores, nil)
|
||||
hash, err := generateManifestHash(manifest, ignores, nil, normalizers.IgnoreNormalizerOpts{})
|
||||
assert.Equal(t, expected, hash)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package controller
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/util/env"
|
||||
@@ -101,8 +102,11 @@ func (c *clusterInfoUpdater) updateClusters() {
|
||||
}
|
||||
_ = kube.RunAllAsync(len(clustersFiltered), func(i int) error {
|
||||
cluster := clustersFiltered[i]
|
||||
if err := c.updateClusterInfo(ctx, cluster, infoByServer[cluster.Server]); err != nil {
|
||||
log.Warnf("Failed to save clusters info: %v", err)
|
||||
clusterInfo := infoByServer[cluster.Server]
|
||||
if err := c.updateClusterInfo(ctx, cluster, clusterInfo); err != nil {
|
||||
log.Warnf("Failed to save cluster info: %v", err)
|
||||
} else if err := updateClusterLabels(ctx, clusterInfo, cluster, c.db.UpdateCluster); err != nil {
|
||||
log.Warnf("Failed to update cluster labels: %v", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -114,6 +118,12 @@ func (c *clusterInfoUpdater) updateClusterInfo(ctx context.Context, cluster appv
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while fetching the apps list: %w", err)
|
||||
}
|
||||
|
||||
updated := c.getUpdatedClusterInfo(ctx, apps, cluster, info, metav1.Now())
|
||||
return c.cache.SetClusterInfo(cluster.Server, &updated)
|
||||
}
|
||||
|
||||
func (c *clusterInfoUpdater) getUpdatedClusterInfo(ctx context.Context, apps []*appv1.Application, cluster appv1.Cluster, info *cache.ClusterInfo, now metav1.Time) appv1.ClusterInfo {
|
||||
var appCount int64
|
||||
for _, a := range apps {
|
||||
if c.projGetter != nil {
|
||||
@@ -129,7 +139,6 @@ func (c *clusterInfoUpdater) updateClusterInfo(ctx context.Context, cluster appv
|
||||
appCount += 1
|
||||
}
|
||||
}
|
||||
now := metav1.Now()
|
||||
clusterInfo := appv1.ClusterInfo{
|
||||
ConnectionState: appv1.ConnectionState{ModifiedAt: &now},
|
||||
ApplicationsCount: appCount,
|
||||
@@ -156,5 +165,15 @@ func (c *clusterInfoUpdater) updateClusterInfo(ctx context.Context, cluster appv
|
||||
}
|
||||
}
|
||||
|
||||
return c.cache.SetClusterInfo(cluster.Server, &clusterInfo)
|
||||
return clusterInfo
|
||||
}
|
||||
|
||||
func updateClusterLabels(ctx context.Context, clusterInfo *cache.ClusterInfo, cluster appv1.Cluster, updateCluster func(context.Context, *appv1.Cluster) (*appv1.Cluster, error)) error {
|
||||
if clusterInfo != nil && cluster.Labels[common.LabelKeyAutoLabelClusterInfo] == "true" && cluster.Labels[common.LabelKeyClusterKubernetesVersion] != clusterInfo.K8SVersion {
|
||||
cluster.Labels[common.LabelKeyClusterKubernetesVersion] = clusterInfo.K8SVersion
|
||||
_, err := updateCluster(ctx, &cluster)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -98,3 +99,92 @@ func TestClusterSecretUpdater(t *testing.T) {
|
||||
assert.Equal(t, test.ExpectedStatus, clusterInfo.ConnectionState.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateClusterLabels(t *testing.T) {
|
||||
shouldNotBeInvoked := func(ctx context.Context, cluster *v1alpha1.Cluster) (*v1alpha1.Cluster, error) {
|
||||
shouldNotHappen := errors.New("if an error happens here, something's wrong")
|
||||
assert.NoError(t, shouldNotHappen)
|
||||
return nil, shouldNotHappen
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
clusterInfo *clustercache.ClusterInfo
|
||||
cluster v1alpha1.Cluster
|
||||
updateCluster func(context.Context, *v1alpha1.Cluster) (*v1alpha1.Cluster, error)
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
"enableClusterInfoLabels = false",
|
||||
&clustercache.ClusterInfo{
|
||||
Server: "kubernetes.svc.local",
|
||||
K8SVersion: "1.28",
|
||||
},
|
||||
v1alpha1.Cluster{
|
||||
Server: "kubernetes.svc.local",
|
||||
Labels: nil,
|
||||
},
|
||||
shouldNotBeInvoked,
|
||||
assert.NoError,
|
||||
},
|
||||
{
|
||||
"clusterInfo = nil",
|
||||
nil,
|
||||
v1alpha1.Cluster{
|
||||
Server: "kubernetes.svc.local",
|
||||
Labels: map[string]string{"argocd.argoproj.io/auto-label-cluster-info": "true"},
|
||||
},
|
||||
shouldNotBeInvoked,
|
||||
assert.NoError,
|
||||
},
|
||||
{
|
||||
"clusterInfo.k8sversion == cluster k8s label",
|
||||
&clustercache.ClusterInfo{
|
||||
Server: "kubernetes.svc.local",
|
||||
K8SVersion: "1.28",
|
||||
},
|
||||
v1alpha1.Cluster{
|
||||
Server: "kubernetes.svc.local",
|
||||
Labels: map[string]string{"argocd.argoproj.io/kubernetes-version": "1.28", "argocd.argoproj.io/auto-label-cluster-info": "true"},
|
||||
},
|
||||
shouldNotBeInvoked,
|
||||
assert.NoError,
|
||||
},
|
||||
{
|
||||
"clusterInfo.k8sversion != cluster k8s label, no error",
|
||||
&clustercache.ClusterInfo{
|
||||
Server: "kubernetes.svc.local",
|
||||
K8SVersion: "1.28",
|
||||
},
|
||||
v1alpha1.Cluster{
|
||||
Server: "kubernetes.svc.local",
|
||||
Labels: map[string]string{"argocd.argoproj.io/kubernetes-version": "1.27", "argocd.argoproj.io/auto-label-cluster-info": "true"},
|
||||
},
|
||||
func(ctx context.Context, cluster *v1alpha1.Cluster) (*v1alpha1.Cluster, error) {
|
||||
assert.Equal(t, cluster.Labels["argocd.argoproj.io/kubernetes-version"], "1.28")
|
||||
return nil, nil
|
||||
},
|
||||
assert.NoError,
|
||||
},
|
||||
{
|
||||
"clusterInfo.k8sversion != cluster k8s label, some error",
|
||||
&clustercache.ClusterInfo{
|
||||
Server: "kubernetes.svc.local",
|
||||
K8SVersion: "1.28",
|
||||
},
|
||||
v1alpha1.Cluster{
|
||||
Server: "kubernetes.svc.local",
|
||||
Labels: map[string]string{"argocd.argoproj.io/kubernetes-version": "1.27", "argocd.argoproj.io/auto-label-cluster-info": "true"},
|
||||
},
|
||||
func(ctx context.Context, cluster *v1alpha1.Cluster) (*v1alpha1.Cluster, error) {
|
||||
assert.Equal(t, cluster.Labels["argocd.argoproj.io/kubernetes-version"], "1.28")
|
||||
return nil, errors.New("some error happened while saving")
|
||||
},
|
||||
assert.Error,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.wantErr(t, updateClusterLabels(context.Background(), tt.clusterInfo, tt.cluster, tt.updateCluster), fmt.Sprintf("updateClusterLabels(%v, %v, %v)", context.Background(), tt.clusterInfo, tt.cluster))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/util/git"
|
||||
"github.com/argoproj/argo-cd/v2/util/healthz"
|
||||
"github.com/argoproj/argo-cd/v2/util/profile"
|
||||
|
||||
ctrl_metrics "sigs.k8s.io/controller-runtime/pkg/metrics"
|
||||
)
|
||||
|
||||
type MetricsServer struct {
|
||||
@@ -160,12 +162,12 @@ func NewMetricsServer(addr string, appLister applister.ApplicationLister, appFil
|
||||
|
||||
mux := http.NewServeMux()
|
||||
registry := NewAppRegistry(appLister, appFilter, appLabels)
|
||||
registry.MustRegister(depth, adds, latency, workDuration, unfinished, longestRunningProcessor, retries)
|
||||
|
||||
mux.Handle(MetricsPath, promhttp.HandlerFor(prometheus.Gatherers{
|
||||
// contains app controller specific metrics
|
||||
registry,
|
||||
// contains process, golang and controller workqueues metrics
|
||||
prometheus.DefaultGatherer,
|
||||
// contains workqueue metrics, process and golang metrics
|
||||
ctrl_metrics.Registry,
|
||||
}, promhttp.HandlerOpts{}))
|
||||
profile.RegisterProfiler(mux)
|
||||
healthz.ServeHealthCheck(mux, healthCheck)
|
||||
|
||||
@@ -2,6 +2,7 @@ package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -15,12 +16,15 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/fake"
|
||||
appinformer "github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions"
|
||||
applister "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
)
|
||||
|
||||
const fakeApp = `
|
||||
@@ -140,6 +144,12 @@ var appFilter = func(obj interface{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Create a fake controller so we initialize the internal controller metrics.
|
||||
// https://github.com/kubernetes-sigs/controller-runtime/blob/4000e996a202917ad7d40f02ed8a2079a9ce25e9/pkg/internal/controller/metrics/metrics.go
|
||||
_, _ = controller.New("test-controller", nil, controller.Options{})
|
||||
}
|
||||
|
||||
func newFakeApp(fakeAppYAML string) *argoappv1.Application {
|
||||
var app argoappv1.Application
|
||||
err := yaml.Unmarshal([]byte(fakeAppYAML), &app)
|
||||
@@ -360,7 +370,7 @@ func assertMetricsPrinted(t *testing.T, expectedLines, body string) {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
assert.Contains(t, body, line, "expected metrics mismatch")
|
||||
assert.Contains(t, body, line, fmt.Sprintf("expected metrics mismatch for line: %s", line))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,3 +453,70 @@ argocd_app_sync_total{dest_server="https://localhost:6443",name="my-app",namespa
|
||||
err = metricsServ.SetExpiration(time.Second)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestWorkqueueMetrics(t *testing.T) {
|
||||
cancel, appLister := newFakeLister()
|
||||
defer cancel()
|
||||
metricsServ, err := NewMetricsServer("localhost:8082", appLister, appFilter, noOpHealthCheck, []string{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedMetrics := `
|
||||
# TYPE workqueue_adds_total counter
|
||||
workqueue_adds_total{name="test"}
|
||||
|
||||
# TYPE workqueue_depth gauge
|
||||
workqueue_depth{name="test"}
|
||||
|
||||
# TYPE workqueue_longest_running_processor_seconds gauge
|
||||
workqueue_longest_running_processor_seconds{name="test"}
|
||||
|
||||
# TYPE workqueue_queue_duration_seconds histogram
|
||||
|
||||
# TYPE workqueue_unfinished_work_seconds gauge
|
||||
workqueue_unfinished_work_seconds{name="test"}
|
||||
|
||||
# TYPE workqueue_work_duration_seconds histogram
|
||||
`
|
||||
workqueue.NewNamed("test")
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "/metrics", nil)
|
||||
assert.NoError(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
metricsServ.Handler.ServeHTTP(rr, req)
|
||||
assert.Equal(t, rr.Code, http.StatusOK)
|
||||
body := rr.Body.String()
|
||||
log.Println(body)
|
||||
assertMetricsPrinted(t, expectedMetrics, body)
|
||||
}
|
||||
|
||||
func TestGoMetrics(t *testing.T) {
|
||||
cancel, appLister := newFakeLister()
|
||||
defer cancel()
|
||||
metricsServ, err := NewMetricsServer("localhost:8082", appLister, appFilter, noOpHealthCheck, []string{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedMetrics := `
|
||||
# TYPE go_gc_duration_seconds summary
|
||||
go_gc_duration_seconds_sum
|
||||
go_gc_duration_seconds_count
|
||||
# TYPE go_goroutines gauge
|
||||
go_goroutines
|
||||
# TYPE go_info gauge
|
||||
go_info
|
||||
# TYPE go_memstats_alloc_bytes gauge
|
||||
go_memstats_alloc_bytes
|
||||
# TYPE go_memstats_sys_bytes gauge
|
||||
go_memstats_sys_bytes
|
||||
# TYPE go_threads gauge
|
||||
go_threads
|
||||
`
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "/metrics", nil)
|
||||
assert.NoError(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
metricsServ.Handler.ServeHTTP(rr, req)
|
||||
assert.Equal(t, rr.Code, http.StatusOK)
|
||||
body := rr.Body.String()
|
||||
log.Println(body)
|
||||
assertMetricsPrinted(t, expectedMetrics, body)
|
||||
}
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
)
|
||||
|
||||
const (
|
||||
WorkQueueSubsystem = "workqueue"
|
||||
DepthKey = "depth"
|
||||
AddsKey = "adds_total"
|
||||
QueueLatencyKey = "queue_duration_seconds"
|
||||
WorkDurationKey = "work_duration_seconds"
|
||||
UnfinishedWorkKey = "unfinished_work_seconds"
|
||||
LongestRunningProcessorKey = "longest_running_processor_seconds"
|
||||
RetriesKey = "retries_total"
|
||||
)
|
||||
|
||||
var (
|
||||
depth = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Subsystem: WorkQueueSubsystem,
|
||||
Name: DepthKey,
|
||||
Help: "Current depth of workqueue",
|
||||
}, []string{"name"})
|
||||
|
||||
adds = prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Subsystem: WorkQueueSubsystem,
|
||||
Name: AddsKey,
|
||||
Help: "Total number of adds handled by workqueue",
|
||||
}, []string{"name"})
|
||||
|
||||
latency = prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Subsystem: WorkQueueSubsystem,
|
||||
Name: QueueLatencyKey,
|
||||
Help: "How long in seconds an item stays in workqueue before being requested",
|
||||
Buckets: []float64{1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1, 5, 10, 15, 30, 60, 120, 180},
|
||||
}, []string{"name"})
|
||||
|
||||
workDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Subsystem: WorkQueueSubsystem,
|
||||
Name: WorkDurationKey,
|
||||
Help: "How long in seconds processing an item from workqueue takes.",
|
||||
Buckets: []float64{1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1, 5, 10, 15, 30, 60, 120, 180},
|
||||
}, []string{"name"})
|
||||
|
||||
unfinished = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Subsystem: WorkQueueSubsystem,
|
||||
Name: UnfinishedWorkKey,
|
||||
Help: "How many seconds of work has been done that " +
|
||||
"is in progress and hasn't been observed by work_duration. Large " +
|
||||
"values indicate stuck threads. One can deduce the number of stuck " +
|
||||
"threads by observing the rate at which this increases.",
|
||||
}, []string{"name"})
|
||||
|
||||
longestRunningProcessor = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Subsystem: WorkQueueSubsystem,
|
||||
Name: LongestRunningProcessorKey,
|
||||
Help: "How many seconds has the longest running " +
|
||||
"processor for workqueue been running.",
|
||||
}, []string{"name"})
|
||||
|
||||
retries = prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Subsystem: WorkQueueSubsystem,
|
||||
Name: RetriesKey,
|
||||
Help: "Total number of retries handled by workqueue",
|
||||
}, []string{"name"})
|
||||
)
|
||||
|
||||
func init() {
|
||||
workqueue.SetProvider(workqueueMetricsProvider{})
|
||||
}
|
||||
|
||||
type workqueueMetricsProvider struct{}
|
||||
|
||||
func (workqueueMetricsProvider) NewDepthMetric(name string) workqueue.GaugeMetric {
|
||||
return depth.WithLabelValues(name)
|
||||
}
|
||||
|
||||
func (workqueueMetricsProvider) NewAddsMetric(name string) workqueue.CounterMetric {
|
||||
return adds.WithLabelValues(name)
|
||||
}
|
||||
|
||||
func (workqueueMetricsProvider) NewLatencyMetric(name string) workqueue.HistogramMetric {
|
||||
return latency.WithLabelValues(name)
|
||||
}
|
||||
|
||||
func (workqueueMetricsProvider) NewWorkDurationMetric(name string) workqueue.HistogramMetric {
|
||||
return workDuration.WithLabelValues(name)
|
||||
}
|
||||
|
||||
func (workqueueMetricsProvider) NewUnfinishedWorkSecondsMetric(name string) workqueue.SettableGaugeMetric {
|
||||
return unfinished.WithLabelValues(name)
|
||||
}
|
||||
|
||||
func (workqueueMetricsProvider) NewLongestRunningProcessorSecondsMetric(name string) workqueue.SettableGaugeMetric {
|
||||
return longestRunningProcessor.WithLabelValues(name)
|
||||
}
|
||||
|
||||
func (workqueueMetricsProvider) NewRetriesMetric(name string) workqueue.CounterMetric {
|
||||
return retries.WithLabelValues(name)
|
||||
}
|
||||
264
controller/sharding/cache.go
Normal file
@@ -0,0 +1,264 @@
|
||||
package sharding
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/db"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ClusterShardingCache interface {
|
||||
Init(clusters *v1alpha1.ClusterList, apps *v1alpha1.ApplicationList)
|
||||
Add(c *v1alpha1.Cluster)
|
||||
Delete(clusterServer string)
|
||||
Update(oldCluster *v1alpha1.Cluster, newCluster *v1alpha1.Cluster)
|
||||
AddApp(a *v1alpha1.Application)
|
||||
DeleteApp(a *v1alpha1.Application)
|
||||
UpdateApp(a *v1alpha1.Application)
|
||||
IsManagedCluster(c *v1alpha1.Cluster) bool
|
||||
GetDistribution() map[string]int
|
||||
GetAppDistribution() map[string]int
|
||||
}
|
||||
|
||||
type ClusterSharding struct {
|
||||
Shard int
|
||||
Replicas int
|
||||
Shards map[string]int
|
||||
Clusters map[string]*v1alpha1.Cluster
|
||||
Apps map[string]*v1alpha1.Application
|
||||
lock sync.RWMutex
|
||||
getClusterShard DistributionFunction
|
||||
}
|
||||
|
||||
func NewClusterSharding(_ db.ArgoDB, shard, replicas int, shardingAlgorithm string) ClusterShardingCache {
|
||||
log.Debugf("Processing clusters from shard %d: Using filter function: %s", shard, shardingAlgorithm)
|
||||
clusterSharding := &ClusterSharding{
|
||||
Shard: shard,
|
||||
Replicas: replicas,
|
||||
Shards: make(map[string]int),
|
||||
Clusters: make(map[string]*v1alpha1.Cluster),
|
||||
Apps: make(map[string]*v1alpha1.Application),
|
||||
}
|
||||
distributionFunction := NoShardingDistributionFunction()
|
||||
if replicas > 1 {
|
||||
log.Debugf("Processing clusters from shard %d: Using filter function: %s", shard, shardingAlgorithm)
|
||||
distributionFunction = GetDistributionFunction(clusterSharding.getClusterAccessor(), clusterSharding.getAppAccessor(), shardingAlgorithm, replicas)
|
||||
} else {
|
||||
log.Info("Processing all cluster shards")
|
||||
}
|
||||
clusterSharding.getClusterShard = distributionFunction
|
||||
return clusterSharding
|
||||
}
|
||||
|
||||
// IsManagedCluster returns wheter or not the cluster should be processed by a given shard.
|
||||
func (s *ClusterSharding) IsManagedCluster(c *v1alpha1.Cluster) bool {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
if c == nil { // nil cluster (in-cluster) is always managed by current clusterShard
|
||||
return true
|
||||
}
|
||||
clusterShard := 0
|
||||
if shard, ok := s.Shards[c.Server]; ok {
|
||||
clusterShard = shard
|
||||
} else {
|
||||
log.Warnf("The cluster %s has no assigned shard.", c.Server)
|
||||
}
|
||||
log.Debugf("Checking if cluster %s with clusterShard %d should be processed by shard %d", c.Server, clusterShard, s.Shard)
|
||||
return clusterShard == s.Shard
|
||||
}
|
||||
|
||||
func (sharding *ClusterSharding) Init(clusters *v1alpha1.ClusterList, apps *v1alpha1.ApplicationList) {
|
||||
sharding.lock.Lock()
|
||||
defer sharding.lock.Unlock()
|
||||
newClusters := make(map[string]*v1alpha1.Cluster, len(clusters.Items))
|
||||
for _, c := range clusters.Items {
|
||||
cluster := c
|
||||
newClusters[c.Server] = &cluster
|
||||
}
|
||||
sharding.Clusters = newClusters
|
||||
|
||||
newApps := make(map[string]*v1alpha1.Application, len(apps.Items))
|
||||
for i := range apps.Items {
|
||||
app := apps.Items[i]
|
||||
newApps[app.Name] = &app
|
||||
}
|
||||
sharding.Apps = newApps
|
||||
sharding.updateDistribution()
|
||||
}
|
||||
|
||||
func (sharding *ClusterSharding) Add(c *v1alpha1.Cluster) {
|
||||
sharding.lock.Lock()
|
||||
defer sharding.lock.Unlock()
|
||||
|
||||
old, ok := sharding.Clusters[c.Server]
|
||||
sharding.Clusters[c.Server] = c
|
||||
if !ok || hasShardingUpdates(old, c) {
|
||||
sharding.updateDistribution()
|
||||
} else {
|
||||
log.Debugf("Skipping sharding distribution update. Cluster already added")
|
||||
}
|
||||
}
|
||||
|
||||
func (sharding *ClusterSharding) Delete(clusterServer string) {
|
||||
sharding.lock.Lock()
|
||||
defer sharding.lock.Unlock()
|
||||
if _, ok := sharding.Clusters[clusterServer]; ok {
|
||||
delete(sharding.Clusters, clusterServer)
|
||||
delete(sharding.Shards, clusterServer)
|
||||
sharding.updateDistribution()
|
||||
}
|
||||
}
|
||||
|
||||
func (sharding *ClusterSharding) Update(oldCluster *v1alpha1.Cluster, newCluster *v1alpha1.Cluster) {
|
||||
sharding.lock.Lock()
|
||||
defer sharding.lock.Unlock()
|
||||
|
||||
if _, ok := sharding.Clusters[oldCluster.Server]; ok && oldCluster.Server != newCluster.Server {
|
||||
delete(sharding.Clusters, oldCluster.Server)
|
||||
delete(sharding.Shards, oldCluster.Server)
|
||||
}
|
||||
sharding.Clusters[newCluster.Server] = newCluster
|
||||
if hasShardingUpdates(oldCluster, newCluster) {
|
||||
sharding.updateDistribution()
|
||||
} else {
|
||||
log.Debugf("Skipping sharding distribution update. No relevant changes")
|
||||
}
|
||||
}
|
||||
|
||||
func (sharding *ClusterSharding) GetDistribution() map[string]int {
|
||||
sharding.lock.RLock()
|
||||
defer sharding.lock.RUnlock()
|
||||
shards := sharding.Shards
|
||||
|
||||
distribution := make(map[string]int, len(shards))
|
||||
for k, v := range shards {
|
||||
distribution[k] = v
|
||||
}
|
||||
return distribution
|
||||
}
|
||||
|
||||
func (sharding *ClusterSharding) updateDistribution() {
|
||||
for k, c := range sharding.Clusters {
|
||||
shard := 0
|
||||
if c.Shard != nil {
|
||||
requestedShard := int(*c.Shard)
|
||||
if requestedShard < sharding.Replicas {
|
||||
shard = requestedShard
|
||||
} else {
|
||||
log.Warnf("Specified cluster shard (%d) for cluster: %s is greater than the number of available shard (%d). Using shard 0.", requestedShard, c.Server, sharding.Replicas)
|
||||
}
|
||||
} else {
|
||||
shard = sharding.getClusterShard(c)
|
||||
}
|
||||
|
||||
existingShard, ok := sharding.Shards[k]
|
||||
if ok && existingShard != shard {
|
||||
log.Infof("Cluster %s has changed shard from %d to %d", k, existingShard, shard)
|
||||
} else if !ok {
|
||||
log.Infof("Cluster %s has been assigned to shard %d", k, shard)
|
||||
} else {
|
||||
log.Debugf("Cluster %s has not changed shard", k)
|
||||
}
|
||||
sharding.Shards[k] = shard
|
||||
}
|
||||
}
|
||||
|
||||
// hasShardingUpdates returns true if the sharding distribution has explicitly changed
|
||||
func hasShardingUpdates(old, new *v1alpha1.Cluster) bool {
|
||||
if old == nil || new == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// returns true if the cluster id has changed because some sharding algorithms depend on it.
|
||||
if old.ID != new.ID {
|
||||
return true
|
||||
}
|
||||
|
||||
if old.Server != new.Server {
|
||||
return true
|
||||
}
|
||||
|
||||
// return false if the shard field has not been modified
|
||||
if old.Shard == nil && new.Shard == nil {
|
||||
return false
|
||||
}
|
||||
return old.Shard == nil || new.Shard == nil || int64(*old.Shard) != int64(*new.Shard)
|
||||
}
|
||||
|
||||
// A read lock should be acquired before calling getClusterAccessor.
|
||||
func (d *ClusterSharding) getClusterAccessor() clusterAccessor {
|
||||
return func() []*v1alpha1.Cluster {
|
||||
// no need to lock, as this is only called from the updateDistribution function
|
||||
clusters := make([]*v1alpha1.Cluster, 0, len(d.Clusters))
|
||||
for _, c := range d.Clusters {
|
||||
clusters = append(clusters, c)
|
||||
}
|
||||
return clusters
|
||||
}
|
||||
}
|
||||
|
||||
// A read lock should be acquired before calling getAppAccessor.
|
||||
func (d *ClusterSharding) getAppAccessor() appAccessor {
|
||||
return func() []*v1alpha1.Application {
|
||||
apps := make([]*v1alpha1.Application, 0, len(d.Apps))
|
||||
for _, a := range d.Apps {
|
||||
apps = append(apps, a)
|
||||
}
|
||||
return apps
|
||||
}
|
||||
}
|
||||
|
||||
func (sharding *ClusterSharding) AddApp(a *v1alpha1.Application) {
|
||||
sharding.lock.Lock()
|
||||
defer sharding.lock.Unlock()
|
||||
|
||||
_, ok := sharding.Apps[a.Name]
|
||||
sharding.Apps[a.Name] = a
|
||||
if !ok {
|
||||
sharding.updateDistribution()
|
||||
} else {
|
||||
log.Debugf("Skipping sharding distribution update. App already added")
|
||||
}
|
||||
}
|
||||
|
||||
func (sharding *ClusterSharding) DeleteApp(a *v1alpha1.Application) {
|
||||
sharding.lock.Lock()
|
||||
defer sharding.lock.Unlock()
|
||||
if _, ok := sharding.Apps[a.Name]; ok {
|
||||
delete(sharding.Apps, a.Name)
|
||||
sharding.updateDistribution()
|
||||
}
|
||||
}
|
||||
|
||||
func (sharding *ClusterSharding) UpdateApp(a *v1alpha1.Application) {
|
||||
sharding.lock.Lock()
|
||||
defer sharding.lock.Unlock()
|
||||
|
||||
_, ok := sharding.Apps[a.Name]
|
||||
sharding.Apps[a.Name] = a
|
||||
if !ok {
|
||||
sharding.updateDistribution()
|
||||
} else {
|
||||
log.Debugf("Skipping sharding distribution update. No relevant changes")
|
||||
}
|
||||
}
|
||||
|
||||
// GetAppDistribution should be not be called from a DestributionFunction because
|
||||
// it could cause a deadlock when updateDistribution is called.
|
||||
func (sharding *ClusterSharding) GetAppDistribution() map[string]int {
|
||||
sharding.lock.RLock()
|
||||
clusters := sharding.Clusters
|
||||
apps := sharding.Apps
|
||||
sharding.lock.RUnlock()
|
||||
|
||||
appDistribution := make(map[string]int, len(clusters))
|
||||
|
||||
for _, a := range apps {
|
||||
if _, ok := appDistribution[a.Spec.Destination.Server]; !ok {
|
||||
appDistribution[a.Spec.Destination.Server] = 0
|
||||
}
|
||||
appDistribution[a.Spec.Destination.Server]++
|
||||
}
|
||||
return appDistribution
|
||||
}
|
||||
511
controller/sharding/cache_test.go
Normal file
@@ -0,0 +1,511 @@
|
||||
package sharding
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
dbmocks "github.com/argoproj/argo-cd/v2/util/db/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func setupTestSharding(shard int, replicas int) *ClusterSharding {
|
||||
shardingAlgorithm := "legacy" // we are using the legacy algorithm as it is deterministic based on the cluster id which is easier to test
|
||||
db := &dbmocks.ArgoDB{}
|
||||
return NewClusterSharding(db, shard, replicas, shardingAlgorithm).(*ClusterSharding)
|
||||
}
|
||||
|
||||
func TestNewClusterSharding(t *testing.T) {
|
||||
shard := 1
|
||||
replicas := 2
|
||||
sharding := setupTestSharding(shard, replicas)
|
||||
|
||||
assert.NotNil(t, sharding)
|
||||
assert.Equal(t, shard, sharding.Shard)
|
||||
assert.Equal(t, replicas, sharding.Replicas)
|
||||
assert.NotNil(t, sharding.Shards)
|
||||
assert.NotNil(t, sharding.Clusters)
|
||||
}
|
||||
|
||||
func TestClusterSharding_Add(t *testing.T) {
|
||||
shard := 1
|
||||
replicas := 2
|
||||
sharding := setupTestSharding(shard, replicas)
|
||||
|
||||
clusterA := &v1alpha1.Cluster{
|
||||
ID: "2",
|
||||
Server: "https://127.0.0.1:6443",
|
||||
}
|
||||
|
||||
sharding.Add(clusterA)
|
||||
|
||||
clusterB := v1alpha1.Cluster{
|
||||
ID: "1",
|
||||
Server: "https://kubernetes.default.svc",
|
||||
}
|
||||
|
||||
sharding.Add(&clusterB)
|
||||
|
||||
distribution := sharding.GetDistribution()
|
||||
|
||||
assert.Contains(t, sharding.Clusters, clusterA.Server)
|
||||
assert.Contains(t, sharding.Clusters, clusterB.Server)
|
||||
|
||||
clusterDistribution, ok := distribution[clusterA.Server]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 1, clusterDistribution)
|
||||
|
||||
myClusterDistribution, ok := distribution[clusterB.Server]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 0, myClusterDistribution)
|
||||
|
||||
assert.Equal(t, 2, len(distribution))
|
||||
}
|
||||
|
||||
func TestClusterSharding_AddRoundRobin_Redistributes(t *testing.T) {
|
||||
shard := 1
|
||||
replicas := 2
|
||||
|
||||
db := &dbmocks.ArgoDB{}
|
||||
|
||||
sharding := NewClusterSharding(db, shard, replicas, "round-robin").(*ClusterSharding)
|
||||
|
||||
clusterA := &v1alpha1.Cluster{
|
||||
ID: "1",
|
||||
Server: "https://127.0.0.1:6443",
|
||||
}
|
||||
sharding.Add(clusterA)
|
||||
|
||||
clusterB := v1alpha1.Cluster{
|
||||
ID: "3",
|
||||
Server: "https://kubernetes.default.svc",
|
||||
}
|
||||
sharding.Add(&clusterB)
|
||||
|
||||
distributionBefore := sharding.GetDistribution()
|
||||
|
||||
assert.Contains(t, sharding.Clusters, clusterA.Server)
|
||||
assert.Contains(t, sharding.Clusters, clusterB.Server)
|
||||
|
||||
clusterDistributionA, ok := distributionBefore[clusterA.Server]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 0, clusterDistributionA)
|
||||
|
||||
clusterDistributionB, ok := distributionBefore[clusterB.Server]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 1, clusterDistributionB)
|
||||
|
||||
assert.Equal(t, 2, len(distributionBefore))
|
||||
|
||||
clusterC := v1alpha1.Cluster{
|
||||
ID: "2",
|
||||
Server: "https://1.1.1.1",
|
||||
}
|
||||
sharding.Add(&clusterC)
|
||||
|
||||
distributionAfter := sharding.GetDistribution()
|
||||
|
||||
assert.Contains(t, sharding.Clusters, clusterA.Server)
|
||||
assert.Contains(t, sharding.Clusters, clusterB.Server)
|
||||
assert.Contains(t, sharding.Clusters, clusterC.Server)
|
||||
|
||||
clusterDistributionA, ok = distributionAfter[clusterA.Server]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 0, clusterDistributionA)
|
||||
|
||||
clusterDistributionC, ok := distributionAfter[clusterC.Server]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 1, clusterDistributionC) // will be assigned to shard 1 because the .ID is smaller then the "B" cluster
|
||||
|
||||
clusterDistributionB, ok = distributionAfter[clusterB.Server]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 0, clusterDistributionB) // will be reassigned to shard 0 because the .ID is bigger then the "C" cluster
|
||||
}
|
||||
|
||||
func TestClusterSharding_Delete(t *testing.T) {
|
||||
shard := 1
|
||||
replicas := 2
|
||||
sharding := setupTestSharding(shard, replicas)
|
||||
|
||||
sharding.Init(
|
||||
&v1alpha1.ClusterList{
|
||||
Items: []v1alpha1.Cluster{
|
||||
{
|
||||
ID: "2",
|
||||
Server: "https://127.0.0.1:6443",
|
||||
},
|
||||
{
|
||||
ID: "1",
|
||||
Server: "https://kubernetes.default.svc",
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1alpha1.ApplicationList{
|
||||
Items: []v1alpha1.Application{
|
||||
createApp("app2", "https://127.0.0.1:6443"),
|
||||
createApp("app1", "https://kubernetes.default.svc"),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
sharding.Delete("https://kubernetes.default.svc")
|
||||
distribution := sharding.GetDistribution()
|
||||
assert.Equal(t, 1, len(distribution))
|
||||
}
|
||||
|
||||
func TestClusterSharding_Update(t *testing.T) {
|
||||
shard := 1
|
||||
replicas := 2
|
||||
sharding := setupTestSharding(shard, replicas)
|
||||
|
||||
sharding.Init(
|
||||
&v1alpha1.ClusterList{
|
||||
Items: []v1alpha1.Cluster{
|
||||
{
|
||||
ID: "2",
|
||||
Server: "https://127.0.0.1:6443",
|
||||
},
|
||||
{
|
||||
ID: "1",
|
||||
Server: "https://kubernetes.default.svc",
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1alpha1.ApplicationList{
|
||||
Items: []v1alpha1.Application{
|
||||
createApp("app2", "https://127.0.0.1:6443"),
|
||||
createApp("app1", "https://kubernetes.default.svc"),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
distributionBefore := sharding.GetDistribution()
|
||||
assert.Equal(t, 2, len(distributionBefore))
|
||||
|
||||
distributionA, ok := distributionBefore["https://kubernetes.default.svc"]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 0, distributionA)
|
||||
|
||||
sharding.Update(&v1alpha1.Cluster{
|
||||
ID: "1",
|
||||
Server: "https://kubernetes.default.svc",
|
||||
}, &v1alpha1.Cluster{
|
||||
ID: "4",
|
||||
Server: "https://kubernetes.default.svc",
|
||||
})
|
||||
|
||||
distributionAfter := sharding.GetDistribution()
|
||||
assert.Equal(t, 2, len(distributionAfter))
|
||||
|
||||
distributionA, ok = distributionAfter["https://kubernetes.default.svc"]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 1, distributionA)
|
||||
}
|
||||
|
||||
func TestClusterSharding_UpdateServerName(t *testing.T) {
|
||||
shard := 1
|
||||
replicas := 2
|
||||
sharding := setupTestSharding(shard, replicas)
|
||||
|
||||
sharding.Init(
|
||||
&v1alpha1.ClusterList{
|
||||
Items: []v1alpha1.Cluster{
|
||||
{
|
||||
ID: "2",
|
||||
Server: "https://127.0.0.1:6443",
|
||||
},
|
||||
{
|
||||
ID: "1",
|
||||
Server: "https://kubernetes.default.svc",
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1alpha1.ApplicationList{
|
||||
Items: []v1alpha1.Application{
|
||||
createApp("app2", "https://127.0.0.1:6443"),
|
||||
createApp("app1", "https://kubernetes.default.svc"),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
distributionBefore := sharding.GetDistribution()
|
||||
assert.Equal(t, 2, len(distributionBefore))
|
||||
|
||||
distributionA, ok := distributionBefore["https://kubernetes.default.svc"]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 0, distributionA)
|
||||
|
||||
sharding.Update(&v1alpha1.Cluster{
|
||||
ID: "1",
|
||||
Server: "https://kubernetes.default.svc",
|
||||
}, &v1alpha1.Cluster{
|
||||
ID: "1",
|
||||
Server: "https://server2",
|
||||
})
|
||||
|
||||
distributionAfter := sharding.GetDistribution()
|
||||
assert.Equal(t, 2, len(distributionAfter))
|
||||
|
||||
_, ok = distributionAfter["https://kubernetes.default.svc"]
|
||||
assert.False(t, ok) // the old server name should not be present anymore
|
||||
|
||||
_, ok = distributionAfter["https://server2"]
|
||||
assert.True(t, ok) // the new server name should be present
|
||||
}
|
||||
|
||||
func TestClusterSharding_IsManagedCluster(t *testing.T) {
|
||||
replicas := 2
|
||||
sharding0 := setupTestSharding(0, replicas)
|
||||
|
||||
sharding0.Init(
|
||||
&v1alpha1.ClusterList{
|
||||
Items: []v1alpha1.Cluster{
|
||||
{
|
||||
ID: "1",
|
||||
Server: "https://kubernetes.default.svc",
|
||||
},
|
||||
{
|
||||
ID: "2",
|
||||
Server: "https://127.0.0.1:6443",
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1alpha1.ApplicationList{
|
||||
Items: []v1alpha1.Application{
|
||||
createApp("app2", "https://127.0.0.1:6443"),
|
||||
createApp("app1", "https://kubernetes.default.svc"),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert.True(t, sharding0.IsManagedCluster(&v1alpha1.Cluster{
|
||||
ID: "1",
|
||||
Server: "https://kubernetes.default.svc",
|
||||
}))
|
||||
|
||||
assert.False(t, sharding0.IsManagedCluster(&v1alpha1.Cluster{
|
||||
ID: "2",
|
||||
Server: "https://127.0.0.1:6443",
|
||||
}))
|
||||
|
||||
sharding1 := setupTestSharding(1, replicas)
|
||||
|
||||
sharding1.Init(
|
||||
&v1alpha1.ClusterList{
|
||||
Items: []v1alpha1.Cluster{
|
||||
{
|
||||
ID: "2",
|
||||
Server: "https://127.0.0.1:6443",
|
||||
},
|
||||
{
|
||||
ID: "1",
|
||||
Server: "https://kubernetes.default.svc",
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1alpha1.ApplicationList{
|
||||
Items: []v1alpha1.Application{
|
||||
createApp("app2", "https://127.0.0.1:6443"),
|
||||
createApp("app1", "https://kubernetes.default.svc"),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert.False(t, sharding1.IsManagedCluster(&v1alpha1.Cluster{
|
||||
ID: "1",
|
||||
Server: "https://kubernetes.default.svc",
|
||||
}))
|
||||
|
||||
assert.True(t, sharding1.IsManagedCluster(&v1alpha1.Cluster{
|
||||
ID: "2",
|
||||
Server: "https://127.0.0.1:6443",
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
func TestClusterSharding_ClusterShardOfResourceShouldNotBeChanged(t *testing.T) {
|
||||
shard := 1
|
||||
replicas := 2
|
||||
sharding := setupTestSharding(shard, replicas)
|
||||
|
||||
Int64Ptr := func(i int64) *int64 {
|
||||
return &i
|
||||
}
|
||||
|
||||
clusterWithNil := &v1alpha1.Cluster{
|
||||
ID: "2",
|
||||
Server: "https://127.0.0.1:6443",
|
||||
Shard: nil,
|
||||
}
|
||||
|
||||
clusterWithValue := &v1alpha1.Cluster{
|
||||
ID: "1",
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Shard: Int64Ptr(1),
|
||||
}
|
||||
|
||||
clusterWithToBigValue := &v1alpha1.Cluster{
|
||||
ID: "3",
|
||||
Server: "https://1.1.1.1",
|
||||
Shard: Int64Ptr(999), // shard value is explicitly bigger than the number of replicas
|
||||
}
|
||||
|
||||
sharding.Init(
|
||||
&v1alpha1.ClusterList{
|
||||
Items: []v1alpha1.Cluster{
|
||||
*clusterWithNil,
|
||||
*clusterWithValue,
|
||||
*clusterWithToBigValue,
|
||||
},
|
||||
},
|
||||
&v1alpha1.ApplicationList{
|
||||
Items: []v1alpha1.Application{
|
||||
createApp("app2", "https://127.0.0.1:6443"),
|
||||
createApp("app1", "https://kubernetes.default.svc"),
|
||||
},
|
||||
},
|
||||
)
|
||||
distribution := sharding.GetDistribution()
|
||||
assert.Equal(t, 3, len(distribution))
|
||||
|
||||
assert.Nil(t, sharding.Clusters[clusterWithNil.Server].Shard)
|
||||
|
||||
assert.NotNil(t, sharding.Clusters[clusterWithValue.Server].Shard)
|
||||
assert.Equal(t, int64(1), *sharding.Clusters[clusterWithValue.Server].Shard)
|
||||
assert.Equal(t, 1, distribution[clusterWithValue.Server])
|
||||
|
||||
assert.NotNil(t, sharding.Clusters[clusterWithToBigValue.Server].Shard)
|
||||
assert.Equal(t, int64(999), *sharding.Clusters[clusterWithToBigValue.Server].Shard)
|
||||
assert.Equal(t, 0, distribution[clusterWithToBigValue.Server]) // will be assigned to shard 0 because the value is bigger than the number of replicas
|
||||
}
|
||||
|
||||
func TestHasShardingUpdates(t *testing.T) {
|
||||
Int64Ptr := func(i int64) *int64 {
|
||||
return &i
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
old *v1alpha1.Cluster
|
||||
new *v1alpha1.Cluster
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "No updates",
|
||||
old: &v1alpha1.Cluster{
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Shard: Int64Ptr(1),
|
||||
},
|
||||
new: &v1alpha1.Cluster{
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Shard: Int64Ptr(1),
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Updates",
|
||||
old: &v1alpha1.Cluster{
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Shard: Int64Ptr(1),
|
||||
},
|
||||
new: &v1alpha1.Cluster{
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Shard: Int64Ptr(2),
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Old is nil",
|
||||
old: nil,
|
||||
new: &v1alpha1.Cluster{
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Shard: Int64Ptr(2),
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "New is nil",
|
||||
old: &v1alpha1.Cluster{
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Shard: Int64Ptr(2),
|
||||
},
|
||||
new: nil,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Both are nil",
|
||||
old: nil,
|
||||
new: nil,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Both shards are nil",
|
||||
old: &v1alpha1.Cluster{
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Shard: nil,
|
||||
},
|
||||
new: &v1alpha1.Cluster{
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Shard: nil,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Old shard is nil",
|
||||
old: &v1alpha1.Cluster{
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Shard: nil,
|
||||
},
|
||||
new: &v1alpha1.Cluster{
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Shard: Int64Ptr(2),
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "New shard is nil",
|
||||
old: &v1alpha1.Cluster{
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Shard: Int64Ptr(2),
|
||||
},
|
||||
new: &v1alpha1.Cluster{
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Shard: nil,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Cluster ID has changed",
|
||||
old: &v1alpha1.Cluster{
|
||||
ID: "1",
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Shard: Int64Ptr(2),
|
||||
},
|
||||
new: &v1alpha1.Cluster{
|
||||
ID: "2",
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Shard: Int64Ptr(2),
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Server has changed",
|
||||
old: &v1alpha1.Cluster{
|
||||
ID: "1",
|
||||
Server: "https://server1",
|
||||
Shard: Int64Ptr(2),
|
||||
},
|
||||
new: &v1alpha1.Cluster{
|
||||
ID: "1",
|
||||
Server: "https://server2",
|
||||
Shard: Int64Ptr(2),
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.expected, hasShardingUpdates(tc.old, tc.new))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"math"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/util/db"
|
||||
"github.com/argoproj/argo-cd/v2/util/env"
|
||||
"github.com/argoproj/argo-cd/v2/util/errors"
|
||||
"github.com/argoproj/argo-cd/v2/util/settings"
|
||||
log "github.com/sirupsen/logrus"
|
||||
kubeerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -40,6 +42,8 @@ const ShardControllerMappingKey = "shardControllerMapping"
|
||||
|
||||
type DistributionFunction func(c *v1alpha1.Cluster) int
|
||||
type ClusterFilterFunction func(c *v1alpha1.Cluster) bool
|
||||
type clusterAccessor func() []*v1alpha1.Cluster
|
||||
type appAccessor func() []*v1alpha1.Application
|
||||
|
||||
// shardApplicationControllerMapping stores the mapping of Shard Number to Application Controller in ConfigMap.
|
||||
// It also stores the heartbeat of last synced time of the application controller.
|
||||
@@ -53,8 +57,7 @@ type shardApplicationControllerMapping struct {
|
||||
// and returns wheter or not the cluster should be processed by a given shard. It calls the distributionFunction
|
||||
// to determine which shard will process the cluster, and if the given shard is equal to the calculated shard
|
||||
// the function will return true.
|
||||
func GetClusterFilter(db db.ArgoDB, distributionFunction DistributionFunction, shard int) ClusterFilterFunction {
|
||||
replicas := db.GetApplicationControllerReplicas()
|
||||
func GetClusterFilter(db db.ArgoDB, distributionFunction DistributionFunction, replicas, shard int) ClusterFilterFunction {
|
||||
return func(c *v1alpha1.Cluster) bool {
|
||||
clusterShard := 0
|
||||
if c != nil && c.Shard != nil {
|
||||
@@ -73,14 +76,14 @@ func GetClusterFilter(db db.ArgoDB, distributionFunction DistributionFunction, s
|
||||
|
||||
// GetDistributionFunction returns which DistributionFunction should be used based on the passed algorithm and
|
||||
// the current datas.
|
||||
func GetDistributionFunction(db db.ArgoDB, shardingAlgorithm string) DistributionFunction {
|
||||
log.Infof("Using filter function: %s", shardingAlgorithm)
|
||||
distributionFunction := LegacyDistributionFunction(db)
|
||||
func GetDistributionFunction(clusters clusterAccessor, apps appAccessor, shardingAlgorithm string, replicasCount int) DistributionFunction {
|
||||
log.Debugf("Using filter function: %s", shardingAlgorithm)
|
||||
distributionFunction := LegacyDistributionFunction(replicasCount)
|
||||
switch shardingAlgorithm {
|
||||
case common.RoundRobinShardingAlgorithm:
|
||||
distributionFunction = RoundRobinDistributionFunction(db)
|
||||
distributionFunction = RoundRobinDistributionFunction(clusters, replicasCount)
|
||||
case common.LegacyShardingAlgorithm:
|
||||
distributionFunction = LegacyDistributionFunction(db)
|
||||
distributionFunction = LegacyDistributionFunction(replicasCount)
|
||||
default:
|
||||
log.Warnf("distribution type %s is not supported, defaulting to %s", shardingAlgorithm, common.DefaultShardingAlgorithm)
|
||||
}
|
||||
@@ -92,15 +95,21 @@ func GetDistributionFunction(db db.ArgoDB, shardingAlgorithm string) Distributio
|
||||
// is lightweight and can be distributed easily, however, it does not ensure an homogenous distribution as
|
||||
// some shards may get assigned more clusters than others. It is the legacy function distribution that is
|
||||
// kept for compatibility reasons
|
||||
func LegacyDistributionFunction(db db.ArgoDB) DistributionFunction {
|
||||
replicas := db.GetApplicationControllerReplicas()
|
||||
func LegacyDistributionFunction(replicas int) DistributionFunction {
|
||||
return func(c *v1alpha1.Cluster) int {
|
||||
if replicas == 0 {
|
||||
log.Debugf("Replicas count is : %d, returning -1", replicas)
|
||||
return -1
|
||||
}
|
||||
if c == nil {
|
||||
log.Debug("In-cluster: returning 0")
|
||||
return 0
|
||||
}
|
||||
// if Shard is manually set and the assigned value is lower than the number of replicas,
|
||||
// then its value is returned otherwise it is the default calculated value
|
||||
if c.Shard != nil && int(*c.Shard) < replicas {
|
||||
return int(*c.Shard)
|
||||
}
|
||||
id := c.ID
|
||||
log.Debugf("Calculating cluster shard for cluster id: %s", id)
|
||||
if id == "" {
|
||||
@@ -121,14 +130,19 @@ func LegacyDistributionFunction(db db.ArgoDB) DistributionFunction {
|
||||
// This function ensures an homogenous distribution: each shards got assigned the same number of
|
||||
// clusters +/-1 , but with the drawback of a reshuffling of clusters accross shards in case of some changes
|
||||
// in the cluster list
|
||||
func RoundRobinDistributionFunction(db db.ArgoDB) DistributionFunction {
|
||||
replicas := db.GetApplicationControllerReplicas()
|
||||
|
||||
func RoundRobinDistributionFunction(clusters clusterAccessor, replicas int) DistributionFunction {
|
||||
return func(c *v1alpha1.Cluster) int {
|
||||
if replicas > 0 {
|
||||
if c == nil { // in-cluster does not necessarly have a secret assigned. So we are receiving a nil cluster here.
|
||||
return 0
|
||||
}
|
||||
// if Shard is manually set and the assigned value is lower than the number of replicas,
|
||||
// then its value is returned otherwise it is the default calculated value
|
||||
if c.Shard != nil && int(*c.Shard) < replicas {
|
||||
return int(*c.Shard)
|
||||
} else {
|
||||
clusterIndexdByClusterIdMap := createClusterIndexByClusterIdMap(db)
|
||||
clusterIndexdByClusterIdMap := createClusterIndexByClusterIdMap(clusters)
|
||||
clusterIndex, ok := clusterIndexdByClusterIdMap[c.ID]
|
||||
if !ok {
|
||||
log.Warnf("Cluster with id=%s not found in cluster map.", c.ID)
|
||||
@@ -144,6 +158,12 @@ func RoundRobinDistributionFunction(db db.ArgoDB) DistributionFunction {
|
||||
}
|
||||
}
|
||||
|
||||
// NoShardingDistributionFunction returns a DistributionFunction that will process all cluster by shard 0
|
||||
// the function is created for API compatibility purposes and is not supposed to be activated.
|
||||
func NoShardingDistributionFunction() DistributionFunction {
|
||||
return func(c *v1alpha1.Cluster) int { return 0 }
|
||||
}
|
||||
|
||||
// InferShard extracts the shard index based on its hostname.
|
||||
func InferShard() (int, error) {
|
||||
hostname, err := osHostnameFunction()
|
||||
@@ -152,33 +172,29 @@ func InferShard() (int, error) {
|
||||
}
|
||||
parts := strings.Split(hostname, "-")
|
||||
if len(parts) == 0 {
|
||||
return 0, fmt.Errorf("hostname should ends with shard number separated by '-' but got: %s", hostname)
|
||||
log.Warnf("hostname should end with shard number separated by '-' but got: %s", hostname)
|
||||
return 0, nil
|
||||
}
|
||||
shard, err := strconv.Atoi(parts[len(parts)-1])
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("hostname should ends with shard number separated by '-' but got: %s", hostname)
|
||||
log.Warnf("hostname should end with shard number separated by '-' but got: %s", hostname)
|
||||
return 0, nil
|
||||
}
|
||||
return int(shard), nil
|
||||
}
|
||||
|
||||
func getSortedClustersList(db db.ArgoDB) []v1alpha1.Cluster {
|
||||
ctx := context.Background()
|
||||
clustersList, dbErr := db.ListClusters(ctx)
|
||||
if dbErr != nil {
|
||||
log.Warnf("Error while querying clusters list from database: %v", dbErr)
|
||||
return []v1alpha1.Cluster{}
|
||||
}
|
||||
clusters := clustersList.Items
|
||||
func getSortedClustersList(getCluster clusterAccessor) []*v1alpha1.Cluster {
|
||||
clusters := getCluster()
|
||||
sort.Slice(clusters, func(i, j int) bool {
|
||||
return clusters[i].ID < clusters[j].ID
|
||||
})
|
||||
return clusters
|
||||
}
|
||||
|
||||
func createClusterIndexByClusterIdMap(db db.ArgoDB) map[string]int {
|
||||
clusters := getSortedClustersList(db)
|
||||
func createClusterIndexByClusterIdMap(getCluster clusterAccessor) map[string]int {
|
||||
clusters := getSortedClustersList(getCluster)
|
||||
log.Debugf("ClustersList has %d items", len(clusters))
|
||||
clusterById := make(map[string]v1alpha1.Cluster)
|
||||
clusterById := make(map[string]*v1alpha1.Cluster)
|
||||
clusterIndexedByClusterId := make(map[string]int)
|
||||
for i, cluster := range clusters {
|
||||
log.Debugf("Adding cluster with id=%s and name=%s to cluster's map", cluster.ID, cluster.Name)
|
||||
@@ -193,8 +209,7 @@ func createClusterIndexByClusterIdMap(db db.ArgoDB) map[string]int {
|
||||
// The function takes the shard number from the environment variable (default value -1, if not set) and passes it to this function.
|
||||
// If the shard value passed to this function is -1, that is, the shard was not set as an environment variable,
|
||||
// we default the shard number to 0 for computing the default config map.
|
||||
func GetOrUpdateShardFromConfigMap(kubeClient *kubernetes.Clientset, settingsMgr *settings.SettingsManager, replicas, shard int) (int, error) {
|
||||
|
||||
func GetOrUpdateShardFromConfigMap(kubeClient kubernetes.Interface, settingsMgr *settings.SettingsManager, replicas, shard int) (int, error) {
|
||||
hostname, err := osHostnameFunction()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
@@ -351,3 +366,59 @@ func getDefaultShardMappingData(replicas int) []shardApplicationControllerMappin
|
||||
}
|
||||
return shardMappingData
|
||||
}
|
||||
|
||||
func GetClusterSharding(kubeClient kubernetes.Interface, settingsMgr *settings.SettingsManager, shardingAlgorithm string, enableDynamicClusterDistribution bool) (ClusterShardingCache, error) {
|
||||
var replicasCount int
|
||||
if enableDynamicClusterDistribution {
|
||||
applicationControllerName := env.StringFromEnv(common.EnvAppControllerName, common.DefaultApplicationControllerName)
|
||||
appControllerDeployment, err := kubeClient.AppsV1().Deployments(settingsMgr.GetNamespace()).Get(context.Background(), applicationControllerName, metav1.GetOptions{})
|
||||
|
||||
// if app controller deployment is not found when dynamic cluster distribution is enabled error out
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("(dynamic cluster distribution) failed to get app controller deployment: %v", err)
|
||||
}
|
||||
|
||||
if appControllerDeployment != nil && appControllerDeployment.Spec.Replicas != nil {
|
||||
replicasCount = int(*appControllerDeployment.Spec.Replicas)
|
||||
} else {
|
||||
return nil, fmt.Errorf("(dynamic cluster distribution) failed to get app controller deployment replica count")
|
||||
}
|
||||
|
||||
} else {
|
||||
replicasCount = env.ParseNumFromEnv(common.EnvControllerReplicas, 0, 0, math.MaxInt32)
|
||||
}
|
||||
shardNumber := env.ParseNumFromEnv(common.EnvControllerShard, -1, -math.MaxInt32, math.MaxInt32)
|
||||
if replicasCount > 1 {
|
||||
// check for shard mapping using configmap if application-controller is a deployment
|
||||
// else use existing logic to infer shard from pod name if application-controller is a statefulset
|
||||
if enableDynamicClusterDistribution {
|
||||
var err error
|
||||
// retry 3 times if we find a conflict while updating shard mapping configMap.
|
||||
// If we still see conflicts after the retries, wait for next iteration of heartbeat process.
|
||||
for i := 0; i <= common.AppControllerHeartbeatUpdateRetryCount; i++ {
|
||||
shardNumber, err = GetOrUpdateShardFromConfigMap(kubeClient, settingsMgr, replicasCount, shardNumber)
|
||||
if err != nil && !kubeerrors.IsConflict(err) {
|
||||
err = fmt.Errorf("unable to get shard due to error updating the sharding config map: %s", err)
|
||||
break
|
||||
}
|
||||
log.Warnf("conflict when getting shard from shard mapping configMap. Retrying (%d/3)", i)
|
||||
}
|
||||
errors.CheckError(err)
|
||||
} else {
|
||||
if shardNumber < 0 {
|
||||
var err error
|
||||
shardNumber, err = InferShard()
|
||||
errors.CheckError(err)
|
||||
}
|
||||
if shardNumber > replicasCount {
|
||||
log.Warnf("Calculated shard number %d is greated than the number of replicas count. Defaulting to 0", shardNumber)
|
||||
shardNumber = 0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Info("Processing all cluster shards")
|
||||
shardNumber = 0
|
||||
}
|
||||
db := db.NewDB(settingsMgr.GetNamespace(), settingsMgr, kubeClient)
|
||||
return NewClusterSharding(db, shardNumber, replicasCount, shardingAlgorithm), nil
|
||||
}
|
||||
|
||||
@@ -1,36 +1,45 @@
|
||||
package sharding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
dbmocks "github.com/argoproj/argo-cd/v2/util/db/mocks"
|
||||
"github.com/argoproj/argo-cd/v2/util/settings"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
func TestGetShardByID_NotEmptyID(t *testing.T) {
|
||||
db := &dbmocks.ArgoDB{}
|
||||
db.On("GetApplicationControllerReplicas").Return(1)
|
||||
assert.Equal(t, 0, LegacyDistributionFunction(db)(&v1alpha1.Cluster{ID: "1"}))
|
||||
assert.Equal(t, 0, LegacyDistributionFunction(db)(&v1alpha1.Cluster{ID: "2"}))
|
||||
assert.Equal(t, 0, LegacyDistributionFunction(db)(&v1alpha1.Cluster{ID: "3"}))
|
||||
assert.Equal(t, 0, LegacyDistributionFunction(db)(&v1alpha1.Cluster{ID: "4"}))
|
||||
replicasCount := 1
|
||||
db.On("GetApplicationControllerReplicas").Return(replicasCount)
|
||||
assert.Equal(t, 0, LegacyDistributionFunction(replicasCount)(&v1alpha1.Cluster{ID: "1"}))
|
||||
assert.Equal(t, 0, LegacyDistributionFunction(replicasCount)(&v1alpha1.Cluster{ID: "2"}))
|
||||
assert.Equal(t, 0, LegacyDistributionFunction(replicasCount)(&v1alpha1.Cluster{ID: "3"}))
|
||||
assert.Equal(t, 0, LegacyDistributionFunction(replicasCount)(&v1alpha1.Cluster{ID: "4"}))
|
||||
}
|
||||
|
||||
func TestGetShardByID_EmptyID(t *testing.T) {
|
||||
db := &dbmocks.ArgoDB{}
|
||||
db.On("GetApplicationControllerReplicas").Return(1)
|
||||
replicasCount := 1
|
||||
db.On("GetApplicationControllerReplicas").Return(replicasCount)
|
||||
distributionFunction := LegacyDistributionFunction
|
||||
shard := distributionFunction(db)(&v1alpha1.Cluster{})
|
||||
shard := distributionFunction(replicasCount)(&v1alpha1.Cluster{})
|
||||
assert.Equal(t, 0, shard)
|
||||
}
|
||||
|
||||
@@ -38,7 +47,7 @@ func TestGetShardByID_NoReplicas(t *testing.T) {
|
||||
db := &dbmocks.ArgoDB{}
|
||||
db.On("GetApplicationControllerReplicas").Return(0)
|
||||
distributionFunction := LegacyDistributionFunction
|
||||
shard := distributionFunction(db)(&v1alpha1.Cluster{})
|
||||
shard := distributionFunction(0)(&v1alpha1.Cluster{})
|
||||
assert.Equal(t, -1, shard)
|
||||
}
|
||||
|
||||
@@ -46,16 +55,16 @@ func TestGetShardByID_NoReplicasUsingHashDistributionFunction(t *testing.T) {
|
||||
db := &dbmocks.ArgoDB{}
|
||||
db.On("GetApplicationControllerReplicas").Return(0)
|
||||
distributionFunction := LegacyDistributionFunction
|
||||
shard := distributionFunction(db)(&v1alpha1.Cluster{})
|
||||
shard := distributionFunction(0)(&v1alpha1.Cluster{})
|
||||
assert.Equal(t, -1, shard)
|
||||
}
|
||||
|
||||
func TestGetShardByID_NoReplicasUsingHashDistributionFunctionWithClusters(t *testing.T) {
|
||||
db, cluster1, cluster2, cluster3, cluster4, cluster5 := createTestClusters()
|
||||
clusters, db, cluster1, cluster2, cluster3, cluster4, cluster5 := createTestClusters()
|
||||
// Test with replicas set to 0
|
||||
db.On("GetApplicationControllerReplicas").Return(0)
|
||||
t.Setenv(common.EnvControllerShardingAlgorithm, common.RoundRobinShardingAlgorithm)
|
||||
distributionFunction := RoundRobinDistributionFunction(db)
|
||||
distributionFunction := RoundRobinDistributionFunction(clusters, 0)
|
||||
assert.Equal(t, -1, distributionFunction(nil))
|
||||
assert.Equal(t, -1, distributionFunction(&cluster1))
|
||||
assert.Equal(t, -1, distributionFunction(&cluster2))
|
||||
@@ -65,137 +74,115 @@ func TestGetShardByID_NoReplicasUsingHashDistributionFunctionWithClusters(t *tes
|
||||
}
|
||||
|
||||
func TestGetClusterFilterDefault(t *testing.T) {
|
||||
shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...)
|
||||
//shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...)
|
||||
clusterAccessor, _, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters()
|
||||
os.Unsetenv(common.EnvControllerShardingAlgorithm)
|
||||
db := &dbmocks.ArgoDB{}
|
||||
db.On("GetApplicationControllerReplicas").Return(2)
|
||||
filter := GetClusterFilter(db, GetDistributionFunction(db, common.DefaultShardingAlgorithm), shardIndex)
|
||||
assert.False(t, filter(&v1alpha1.Cluster{ID: "1"}))
|
||||
assert.True(t, filter(&v1alpha1.Cluster{ID: "2"}))
|
||||
assert.False(t, filter(&v1alpha1.Cluster{ID: "3"}))
|
||||
assert.True(t, filter(&v1alpha1.Cluster{ID: "4"}))
|
||||
replicasCount := 2
|
||||
distributionFunction := RoundRobinDistributionFunction(clusterAccessor, replicasCount)
|
||||
assert.Equal(t, 0, distributionFunction(nil))
|
||||
assert.Equal(t, 0, distributionFunction(&cluster1))
|
||||
assert.Equal(t, 1, distributionFunction(&cluster2))
|
||||
assert.Equal(t, 0, distributionFunction(&cluster3))
|
||||
assert.Equal(t, 1, distributionFunction(&cluster4))
|
||||
}
|
||||
|
||||
func TestGetClusterFilterLegacy(t *testing.T) {
|
||||
shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...)
|
||||
db := &dbmocks.ArgoDB{}
|
||||
db.On("GetApplicationControllerReplicas").Return(2)
|
||||
//shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...)
|
||||
clusterAccessor, db, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters()
|
||||
replicasCount := 2
|
||||
db.On("GetApplicationControllerReplicas").Return(replicasCount)
|
||||
t.Setenv(common.EnvControllerShardingAlgorithm, common.LegacyShardingAlgorithm)
|
||||
filter := GetClusterFilter(db, GetDistributionFunction(db, common.LegacyShardingAlgorithm), shardIndex)
|
||||
assert.False(t, filter(&v1alpha1.Cluster{ID: "1"}))
|
||||
assert.True(t, filter(&v1alpha1.Cluster{ID: "2"}))
|
||||
assert.False(t, filter(&v1alpha1.Cluster{ID: "3"}))
|
||||
assert.True(t, filter(&v1alpha1.Cluster{ID: "4"}))
|
||||
distributionFunction := RoundRobinDistributionFunction(clusterAccessor, replicasCount)
|
||||
assert.Equal(t, 0, distributionFunction(nil))
|
||||
assert.Equal(t, 0, distributionFunction(&cluster1))
|
||||
assert.Equal(t, 1, distributionFunction(&cluster2))
|
||||
assert.Equal(t, 0, distributionFunction(&cluster3))
|
||||
assert.Equal(t, 1, distributionFunction(&cluster4))
|
||||
}
|
||||
|
||||
func TestGetClusterFilterUnknown(t *testing.T) {
|
||||
shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...)
|
||||
db := &dbmocks.ArgoDB{}
|
||||
db.On("GetApplicationControllerReplicas").Return(2)
|
||||
clusterAccessor, db, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters()
|
||||
appAccessor, _, _, _, _, _ := createTestApps()
|
||||
// Test with replicas set to 0
|
||||
t.Setenv(common.EnvControllerReplicas, "2")
|
||||
os.Unsetenv(common.EnvControllerShardingAlgorithm)
|
||||
t.Setenv(common.EnvControllerShardingAlgorithm, "unknown")
|
||||
filter := GetClusterFilter(db, GetDistributionFunction(db, "unknown"), shardIndex)
|
||||
assert.False(t, filter(&v1alpha1.Cluster{ID: "1"}))
|
||||
assert.True(t, filter(&v1alpha1.Cluster{ID: "2"}))
|
||||
assert.False(t, filter(&v1alpha1.Cluster{ID: "3"}))
|
||||
assert.True(t, filter(&v1alpha1.Cluster{ID: "4"}))
|
||||
replicasCount := 2
|
||||
db.On("GetApplicationControllerReplicas").Return(replicasCount)
|
||||
distributionFunction := GetDistributionFunction(clusterAccessor, appAccessor, "unknown", replicasCount)
|
||||
assert.Equal(t, 0, distributionFunction(nil))
|
||||
assert.Equal(t, 0, distributionFunction(&cluster1))
|
||||
assert.Equal(t, 1, distributionFunction(&cluster2))
|
||||
assert.Equal(t, 0, distributionFunction(&cluster3))
|
||||
assert.Equal(t, 1, distributionFunction(&cluster4))
|
||||
}
|
||||
|
||||
func TestLegacyGetClusterFilterWithFixedShard(t *testing.T) {
|
||||
shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...)
|
||||
db := &dbmocks.ArgoDB{}
|
||||
db.On("GetApplicationControllerReplicas").Return(2)
|
||||
filter := GetClusterFilter(db, GetDistributionFunction(db, common.DefaultShardingAlgorithm), shardIndex)
|
||||
assert.False(t, filter(nil))
|
||||
assert.False(t, filter(&v1alpha1.Cluster{ID: "1"}))
|
||||
assert.True(t, filter(&v1alpha1.Cluster{ID: "2"}))
|
||||
assert.False(t, filter(&v1alpha1.Cluster{ID: "3"}))
|
||||
assert.True(t, filter(&v1alpha1.Cluster{ID: "4"}))
|
||||
//shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...)
|
||||
t.Setenv(common.EnvControllerReplicas, "5")
|
||||
clusterAccessor, db, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters()
|
||||
appAccessor, _, _, _, _, _ := createTestApps()
|
||||
replicasCount := 5
|
||||
db.On("GetApplicationControllerReplicas").Return(replicasCount)
|
||||
filter := GetDistributionFunction(clusterAccessor, appAccessor, common.DefaultShardingAlgorithm, replicasCount)
|
||||
assert.Equal(t, 0, filter(nil))
|
||||
assert.Equal(t, 4, filter(&cluster1))
|
||||
assert.Equal(t, 1, filter(&cluster2))
|
||||
assert.Equal(t, 2, filter(&cluster3))
|
||||
assert.Equal(t, 2, filter(&cluster4))
|
||||
|
||||
var fixedShard int64 = 4
|
||||
filter = GetClusterFilter(db, GetDistributionFunction(db, common.DefaultShardingAlgorithm), int(fixedShard))
|
||||
assert.False(t, filter(&v1alpha1.Cluster{ID: "4", Shard: &fixedShard}))
|
||||
cluster5 := &v1alpha1.Cluster{ID: "5", Shard: &fixedShard}
|
||||
clusterAccessor = getClusterAccessor([]v1alpha1.Cluster{cluster1, cluster2, cluster2, cluster4, *cluster5})
|
||||
filter = GetDistributionFunction(clusterAccessor, appAccessor, common.DefaultShardingAlgorithm, replicasCount)
|
||||
assert.Equal(t, int(fixedShard), filter(cluster5))
|
||||
|
||||
fixedShard = 1
|
||||
filter = GetClusterFilter(db, GetDistributionFunction(db, common.DefaultShardingAlgorithm), int(fixedShard))
|
||||
assert.True(t, filter(&v1alpha1.Cluster{Name: "cluster4", ID: "4", Shard: &fixedShard}))
|
||||
cluster5.Shard = &fixedShard
|
||||
clusterAccessor = getClusterAccessor([]v1alpha1.Cluster{cluster1, cluster2, cluster2, cluster4, *cluster5})
|
||||
filter = GetDistributionFunction(clusterAccessor, appAccessor, common.DefaultShardingAlgorithm, replicasCount)
|
||||
assert.Equal(t, int(fixedShard), filter(&v1alpha1.Cluster{ID: "4", Shard: &fixedShard}))
|
||||
}
|
||||
|
||||
func TestRoundRobinGetClusterFilterWithFixedShard(t *testing.T) {
|
||||
shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...)
|
||||
db, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters()
|
||||
db.On("GetApplicationControllerReplicas").Return(2)
|
||||
filter := GetClusterFilter(db, GetDistributionFunction(db, common.RoundRobinShardingAlgorithm), shardIndex)
|
||||
assert.False(t, filter(nil))
|
||||
assert.False(t, filter(&cluster1))
|
||||
assert.True(t, filter(&cluster2))
|
||||
assert.False(t, filter(&cluster3))
|
||||
assert.True(t, filter(&cluster4))
|
||||
//shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...)
|
||||
t.Setenv(common.EnvControllerReplicas, "4")
|
||||
clusterAccessor, db, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters()
|
||||
appAccessor, _, _, _, _, _ := createTestApps()
|
||||
replicasCount := 4
|
||||
db.On("GetApplicationControllerReplicas").Return(replicasCount)
|
||||
|
||||
filter := GetDistributionFunction(clusterAccessor, appAccessor, common.RoundRobinShardingAlgorithm, replicasCount)
|
||||
assert.Equal(t, filter(nil), 0)
|
||||
assert.Equal(t, filter(&cluster1), 0)
|
||||
assert.Equal(t, filter(&cluster2), 1)
|
||||
assert.Equal(t, filter(&cluster3), 2)
|
||||
assert.Equal(t, filter(&cluster4), 3)
|
||||
|
||||
// a cluster with a fixed shard should be processed by the specified exact
|
||||
// same shard unless the specified shard index is greater than the number of replicas.
|
||||
var fixedShard int64 = 4
|
||||
filter = GetClusterFilter(db, GetDistributionFunction(db, common.RoundRobinShardingAlgorithm), int(fixedShard))
|
||||
assert.False(t, filter(&v1alpha1.Cluster{Name: "cluster4", ID: "4", Shard: &fixedShard}))
|
||||
var fixedShard int64 = 1
|
||||
cluster5 := v1alpha1.Cluster{Name: "cluster5", ID: "5", Shard: &fixedShard}
|
||||
clusters := []v1alpha1.Cluster{cluster1, cluster2, cluster3, cluster4, cluster5}
|
||||
clusterAccessor = getClusterAccessor(clusters)
|
||||
filter = GetDistributionFunction(clusterAccessor, appAccessor, common.RoundRobinShardingAlgorithm, replicasCount)
|
||||
assert.Equal(t, int(fixedShard), filter(&cluster5))
|
||||
|
||||
fixedShard = 1
|
||||
filter = GetClusterFilter(db, GetDistributionFunction(db, common.RoundRobinShardingAlgorithm), int(fixedShard))
|
||||
assert.True(t, filter(&v1alpha1.Cluster{Name: "cluster4", ID: "4", Shard: &fixedShard}))
|
||||
}
|
||||
|
||||
func TestGetClusterFilterLegacyHash(t *testing.T) {
|
||||
shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...)
|
||||
t.Setenv(common.EnvControllerShardingAlgorithm, "hash")
|
||||
db, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters()
|
||||
db.On("GetApplicationControllerReplicas").Return(2)
|
||||
filter := GetClusterFilter(db, GetDistributionFunction(db, common.LegacyShardingAlgorithm), shardIndex)
|
||||
assert.False(t, filter(&cluster1))
|
||||
assert.True(t, filter(&cluster2))
|
||||
assert.False(t, filter(&cluster3))
|
||||
assert.True(t, filter(&cluster4))
|
||||
|
||||
// a cluster with a fixed shard should be processed by the specified exact
|
||||
// same shard unless the specified shard index is greater than the number of replicas.
|
||||
var fixedShard int64 = 4
|
||||
filter = GetClusterFilter(db, GetDistributionFunction(db, common.LegacyShardingAlgorithm), int(fixedShard))
|
||||
assert.False(t, filter(&v1alpha1.Cluster{Name: "cluster4", ID: "4", Shard: &fixedShard}))
|
||||
|
||||
fixedShard = 1
|
||||
filter = GetClusterFilter(db, GetDistributionFunction(db, common.LegacyShardingAlgorithm), int(fixedShard))
|
||||
assert.True(t, filter(&v1alpha1.Cluster{Name: "cluster4", ID: "4", Shard: &fixedShard}))
|
||||
}
|
||||
|
||||
func TestGetClusterFilterWithEnvControllerShardingAlgorithms(t *testing.T) {
|
||||
db, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters()
|
||||
shardIndex := 1
|
||||
db.On("GetApplicationControllerReplicas").Return(2)
|
||||
|
||||
t.Run("legacy", func(t *testing.T) {
|
||||
t.Setenv(common.EnvControllerShardingAlgorithm, common.LegacyShardingAlgorithm)
|
||||
shardShouldProcessCluster := GetClusterFilter(db, GetDistributionFunction(db, common.LegacyShardingAlgorithm), shardIndex)
|
||||
assert.False(t, shardShouldProcessCluster(&cluster1))
|
||||
assert.True(t, shardShouldProcessCluster(&cluster2))
|
||||
assert.False(t, shardShouldProcessCluster(&cluster3))
|
||||
assert.True(t, shardShouldProcessCluster(&cluster4))
|
||||
assert.False(t, shardShouldProcessCluster(nil))
|
||||
})
|
||||
|
||||
t.Run("roundrobin", func(t *testing.T) {
|
||||
t.Setenv(common.EnvControllerShardingAlgorithm, common.RoundRobinShardingAlgorithm)
|
||||
shardShouldProcessCluster := GetClusterFilter(db, GetDistributionFunction(db, common.LegacyShardingAlgorithm), shardIndex)
|
||||
assert.False(t, shardShouldProcessCluster(&cluster1))
|
||||
assert.True(t, shardShouldProcessCluster(&cluster2))
|
||||
assert.False(t, shardShouldProcessCluster(&cluster3))
|
||||
assert.True(t, shardShouldProcessCluster(&cluster4))
|
||||
assert.False(t, shardShouldProcessCluster(nil))
|
||||
})
|
||||
cluster5 = v1alpha1.Cluster{Name: "cluster5", ID: "5", Shard: &fixedShard}
|
||||
clusters = []v1alpha1.Cluster{cluster1, cluster2, cluster3, cluster4, cluster5}
|
||||
clusterAccessor = getClusterAccessor(clusters)
|
||||
filter = GetDistributionFunction(clusterAccessor, appAccessor, common.RoundRobinShardingAlgorithm, replicasCount)
|
||||
assert.Equal(t, int(fixedShard), filter(&v1alpha1.Cluster{Name: "cluster4", ID: "4", Shard: &fixedShard}))
|
||||
}
|
||||
|
||||
func TestGetShardByIndexModuloReplicasCountDistributionFunction2(t *testing.T) {
|
||||
db, cluster1, cluster2, cluster3, cluster4, cluster5 := createTestClusters()
|
||||
clusters, db, cluster1, cluster2, cluster3, cluster4, cluster5 := createTestClusters()
|
||||
|
||||
t.Run("replicas set to 1", func(t *testing.T) {
|
||||
db.On("GetApplicationControllerReplicas").Return(1).Once()
|
||||
distributionFunction := RoundRobinDistributionFunction(db)
|
||||
replicasCount := 1
|
||||
db.On("GetApplicationControllerReplicas").Return(replicasCount).Once()
|
||||
distributionFunction := RoundRobinDistributionFunction(clusters, replicasCount)
|
||||
assert.Equal(t, 0, distributionFunction(nil))
|
||||
assert.Equal(t, 0, distributionFunction(&cluster1))
|
||||
assert.Equal(t, 0, distributionFunction(&cluster2))
|
||||
@@ -205,8 +192,9 @@ func TestGetShardByIndexModuloReplicasCountDistributionFunction2(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("replicas set to 2", func(t *testing.T) {
|
||||
db.On("GetApplicationControllerReplicas").Return(2).Once()
|
||||
distributionFunction := RoundRobinDistributionFunction(db)
|
||||
replicasCount := 2
|
||||
db.On("GetApplicationControllerReplicas").Return(replicasCount).Once()
|
||||
distributionFunction := RoundRobinDistributionFunction(clusters, replicasCount)
|
||||
assert.Equal(t, 0, distributionFunction(nil))
|
||||
assert.Equal(t, 0, distributionFunction(&cluster1))
|
||||
assert.Equal(t, 1, distributionFunction(&cluster2))
|
||||
@@ -216,8 +204,9 @@ func TestGetShardByIndexModuloReplicasCountDistributionFunction2(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("replicas set to 3", func(t *testing.T) {
|
||||
db.On("GetApplicationControllerReplicas").Return(3).Once()
|
||||
distributionFunction := RoundRobinDistributionFunction(db)
|
||||
replicasCount := 3
|
||||
db.On("GetApplicationControllerReplicas").Return(replicasCount).Once()
|
||||
distributionFunction := RoundRobinDistributionFunction(clusters, replicasCount)
|
||||
assert.Equal(t, 0, distributionFunction(nil))
|
||||
assert.Equal(t, 0, distributionFunction(&cluster1))
|
||||
assert.Equal(t, 1, distributionFunction(&cluster2))
|
||||
@@ -233,17 +222,19 @@ func TestGetShardByIndexModuloReplicasCountDistributionFunctionWhenClusterNumber
|
||||
// Initial tests where showing that under 1024 clusters, execution time was around 400ms
|
||||
// and for 4096 clusters, execution time was under 9s
|
||||
// The other implementation was giving almost linear time of 400ms up to 10'000 clusters
|
||||
db := dbmocks.ArgoDB{}
|
||||
clusterList := &v1alpha1.ClusterList{Items: []v1alpha1.Cluster{}}
|
||||
clusterPointers := []*v1alpha1.Cluster{}
|
||||
for i := 0; i < 2048; i++ {
|
||||
cluster := createCluster(fmt.Sprintf("cluster-%d", i), fmt.Sprintf("%d", i))
|
||||
clusterList.Items = append(clusterList.Items, cluster)
|
||||
clusterPointers = append(clusterPointers, &cluster)
|
||||
}
|
||||
db.On("ListClusters", mock.Anything).Return(clusterList, nil)
|
||||
db.On("GetApplicationControllerReplicas").Return(2)
|
||||
distributionFunction := RoundRobinDistributionFunction(&db)
|
||||
for i, c := range clusterList.Items {
|
||||
assert.Equal(t, i%2, distributionFunction(&c))
|
||||
replicasCount := 2
|
||||
t.Setenv(common.EnvControllerReplicas, strconv.Itoa(replicasCount))
|
||||
_, db, _, _, _, _, _ := createTestClusters()
|
||||
clusterAccessor := func() []*v1alpha1.Cluster { return clusterPointers }
|
||||
db.On("GetApplicationControllerReplicas").Return(replicasCount)
|
||||
distributionFunction := RoundRobinDistributionFunction(clusterAccessor, replicasCount)
|
||||
for i, c := range clusterPointers {
|
||||
assert.Equal(t, i%2, distributionFunction(c))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,12 +247,15 @@ func TestGetShardByIndexModuloReplicasCountDistributionFunctionWhenClusterIsAdde
|
||||
cluster5 := createCluster("cluster5", "5")
|
||||
cluster6 := createCluster("cluster6", "6")
|
||||
|
||||
clusters := []v1alpha1.Cluster{cluster1, cluster2, cluster3, cluster4, cluster5}
|
||||
clusterAccessor := getClusterAccessor(clusters)
|
||||
|
||||
clusterList := &v1alpha1.ClusterList{Items: []v1alpha1.Cluster{cluster1, cluster2, cluster3, cluster4, cluster5}}
|
||||
db.On("ListClusters", mock.Anything).Return(clusterList, nil)
|
||||
|
||||
// Test with replicas set to 2
|
||||
db.On("GetApplicationControllerReplicas").Return(2)
|
||||
distributionFunction := RoundRobinDistributionFunction(&db)
|
||||
replicasCount := 2
|
||||
db.On("GetApplicationControllerReplicas").Return(replicasCount)
|
||||
distributionFunction := RoundRobinDistributionFunction(clusterAccessor, replicasCount)
|
||||
assert.Equal(t, 0, distributionFunction(nil))
|
||||
assert.Equal(t, 0, distributionFunction(&cluster1))
|
||||
assert.Equal(t, 1, distributionFunction(&cluster2))
|
||||
@@ -272,17 +266,20 @@ func TestGetShardByIndexModuloReplicasCountDistributionFunctionWhenClusterIsAdde
|
||||
|
||||
// Now, the database knows cluster6. Shard should be assigned a proper shard
|
||||
clusterList.Items = append(clusterList.Items, cluster6)
|
||||
distributionFunction = RoundRobinDistributionFunction(getClusterAccessor(clusterList.Items), replicasCount)
|
||||
assert.Equal(t, 1, distributionFunction(&cluster6))
|
||||
|
||||
// Now, we remove the last added cluster, it should be unassigned as well
|
||||
clusterList.Items = clusterList.Items[:len(clusterList.Items)-1]
|
||||
distributionFunction = RoundRobinDistributionFunction(getClusterAccessor(clusterList.Items), replicasCount)
|
||||
assert.Equal(t, -1, distributionFunction(&cluster6))
|
||||
}
|
||||
|
||||
func TestGetShardByIndexModuloReplicasCountDistributionFunction(t *testing.T) {
|
||||
db, cluster1, cluster2, _, _, _ := createTestClusters()
|
||||
db.On("GetApplicationControllerReplicas").Return(2)
|
||||
distributionFunction := RoundRobinDistributionFunction(db)
|
||||
clusters, db, cluster1, cluster2, _, _, _ := createTestClusters()
|
||||
replicasCount := 2
|
||||
db.On("GetApplicationControllerReplicas").Return(replicasCount)
|
||||
distributionFunction := RoundRobinDistributionFunction(clusters, replicasCount)
|
||||
|
||||
// Test that the function returns the correct shard for cluster1 and cluster2
|
||||
expectedShardForCluster1 := 0
|
||||
@@ -315,14 +312,14 @@ func TestInferShard(t *testing.T) {
|
||||
|
||||
osHostnameFunction = func() (string, error) { return "exampleshard", nil }
|
||||
_, err = InferShard()
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, err)
|
||||
|
||||
osHostnameFunction = func() (string, error) { return "example-shard", nil }
|
||||
_, err = InferShard()
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func createTestClusters() (*dbmocks.ArgoDB, v1alpha1.Cluster, v1alpha1.Cluster, v1alpha1.Cluster, v1alpha1.Cluster, v1alpha1.Cluster) {
|
||||
func createTestClusters() (clusterAccessor, *dbmocks.ArgoDB, v1alpha1.Cluster, v1alpha1.Cluster, v1alpha1.Cluster, v1alpha1.Cluster, v1alpha1.Cluster) {
|
||||
db := dbmocks.ArgoDB{}
|
||||
cluster1 := createCluster("cluster1", "1")
|
||||
cluster2 := createCluster("cluster2", "2")
|
||||
@@ -330,10 +327,27 @@ func createTestClusters() (*dbmocks.ArgoDB, v1alpha1.Cluster, v1alpha1.Cluster,
|
||||
cluster4 := createCluster("cluster4", "4")
|
||||
cluster5 := createCluster("cluster5", "5")
|
||||
|
||||
clusters := []v1alpha1.Cluster{cluster1, cluster2, cluster3, cluster4, cluster5}
|
||||
|
||||
db.On("ListClusters", mock.Anything).Return(&v1alpha1.ClusterList{Items: []v1alpha1.Cluster{
|
||||
cluster1, cluster2, cluster3, cluster4, cluster5,
|
||||
}}, nil)
|
||||
return &db, cluster1, cluster2, cluster3, cluster4, cluster5
|
||||
return getClusterAccessor(clusters), &db, cluster1, cluster2, cluster3, cluster4, cluster5
|
||||
}
|
||||
|
||||
func getClusterAccessor(clusters []v1alpha1.Cluster) clusterAccessor {
|
||||
// Convert the array to a slice of pointers
|
||||
clusterPointers := getClusterPointers(clusters)
|
||||
clusterAccessor := func() []*v1alpha1.Cluster { return clusterPointers }
|
||||
return clusterAccessor
|
||||
}
|
||||
|
||||
func getClusterPointers(clusters []v1alpha1.Cluster) []*v1alpha1.Cluster {
|
||||
var clusterPointers []*v1alpha1.Cluster
|
||||
for i := range clusters {
|
||||
clusterPointers = append(clusterPointers, &clusters[i])
|
||||
}
|
||||
return clusterPointers
|
||||
}
|
||||
|
||||
func createCluster(name string, id string) v1alpha1.Cluster {
|
||||
@@ -676,3 +690,265 @@ func Test_getOrUpdateShardNumberForController(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetClusterSharding(t *testing.T) {
|
||||
IntPtr := func(i int32) *int32 {
|
||||
return &i
|
||||
}
|
||||
|
||||
deployment := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: common.DefaultApplicationControllerName,
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: IntPtr(1),
|
||||
},
|
||||
}
|
||||
|
||||
deploymentMultiReplicas := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "argocd-application-controller-multi-replicas",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: IntPtr(3),
|
||||
},
|
||||
}
|
||||
|
||||
objects := append([]runtime.Object{}, deployment, deploymentMultiReplicas)
|
||||
kubeclientset := kubefake.NewSimpleClientset(objects...)
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(context.TODO(), kubeclientset, "argocd", settings.WithRepoOrClusterChangedHandler(func() {
|
||||
}))
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
useDynamicSharding bool
|
||||
envsSetter func(t *testing.T)
|
||||
cleanup func()
|
||||
expectedShard int
|
||||
expectedReplicas int
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "Default sharding with statefulset",
|
||||
envsSetter: func(t *testing.T) {
|
||||
t.Setenv(common.EnvControllerReplicas, "1")
|
||||
},
|
||||
cleanup: func() {},
|
||||
useDynamicSharding: false,
|
||||
expectedShard: 0,
|
||||
expectedReplicas: 1,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Default sharding with deployment",
|
||||
envsSetter: func(t *testing.T) {
|
||||
t.Setenv(common.EnvAppControllerName, common.DefaultApplicationControllerName)
|
||||
},
|
||||
cleanup: func() {},
|
||||
useDynamicSharding: true,
|
||||
expectedShard: 0,
|
||||
expectedReplicas: 1,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Default sharding with deployment and multiple replicas",
|
||||
envsSetter: func(t *testing.T) {
|
||||
t.Setenv(common.EnvAppControllerName, "argocd-application-controller-multi-replicas")
|
||||
},
|
||||
cleanup: func() {},
|
||||
useDynamicSharding: true,
|
||||
expectedShard: 0,
|
||||
expectedReplicas: 3,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Statefulset multiple replicas",
|
||||
envsSetter: func(t *testing.T) {
|
||||
t.Setenv(common.EnvControllerReplicas, "3")
|
||||
osHostnameFunction = func() (string, error) { return "example-shard-3", nil }
|
||||
},
|
||||
cleanup: func() {
|
||||
osHostnameFunction = os.Hostname
|
||||
},
|
||||
useDynamicSharding: false,
|
||||
expectedShard: 3,
|
||||
expectedReplicas: 3,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Explicit shard with statefulset and 1 replica",
|
||||
envsSetter: func(t *testing.T) {
|
||||
t.Setenv(common.EnvControllerReplicas, "1")
|
||||
t.Setenv(common.EnvControllerShard, "3")
|
||||
},
|
||||
cleanup: func() {},
|
||||
useDynamicSharding: false,
|
||||
expectedShard: 0,
|
||||
expectedReplicas: 1,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Explicit shard with statefulset and 2 replica - and to high shard",
|
||||
envsSetter: func(t *testing.T) {
|
||||
t.Setenv(common.EnvControllerReplicas, "2")
|
||||
t.Setenv(common.EnvControllerShard, "3")
|
||||
},
|
||||
cleanup: func() {},
|
||||
useDynamicSharding: false,
|
||||
expectedShard: 0,
|
||||
expectedReplicas: 2,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Explicit shard with statefulset and 2 replica",
|
||||
envsSetter: func(t *testing.T) {
|
||||
t.Setenv(common.EnvControllerReplicas, "2")
|
||||
t.Setenv(common.EnvControllerShard, "1")
|
||||
},
|
||||
cleanup: func() {},
|
||||
useDynamicSharding: false,
|
||||
expectedShard: 1,
|
||||
expectedReplicas: 2,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Explicit shard with deployment",
|
||||
envsSetter: func(t *testing.T) {
|
||||
t.Setenv(common.EnvControllerShard, "3")
|
||||
},
|
||||
cleanup: func() {},
|
||||
useDynamicSharding: true,
|
||||
expectedShard: 0,
|
||||
expectedReplicas: 1,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Explicit shard with deployment and multiple replicas will read from configmap",
|
||||
envsSetter: func(t *testing.T) {
|
||||
t.Setenv(common.EnvAppControllerName, "argocd-application-controller-multi-replicas")
|
||||
t.Setenv(common.EnvControllerShard, "3")
|
||||
},
|
||||
cleanup: func() {},
|
||||
useDynamicSharding: true,
|
||||
expectedShard: 0,
|
||||
expectedReplicas: 3,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Dynamic sharding but missing deployment",
|
||||
envsSetter: func(t *testing.T) {
|
||||
t.Setenv(common.EnvAppControllerName, "missing-deployment")
|
||||
},
|
||||
cleanup: func() {},
|
||||
useDynamicSharding: true,
|
||||
expectedShard: 0,
|
||||
expectedReplicas: 1,
|
||||
expectedErr: fmt.Errorf("(dynamic cluster distribution) failed to get app controller deployment: deployments.apps \"missing-deployment\" not found"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tc.envsSetter(t)
|
||||
defer tc.cleanup()
|
||||
shardingCache, err := GetClusterSharding(kubeclientset, settingsMgr, "round-robin", tc.useDynamicSharding)
|
||||
|
||||
if shardingCache != nil {
|
||||
clusterSharding := shardingCache.(*ClusterSharding)
|
||||
assert.Equal(t, tc.expectedShard, clusterSharding.Shard)
|
||||
assert.Equal(t, tc.expectedReplicas, clusterSharding.Replicas)
|
||||
}
|
||||
|
||||
if tc.expectedErr != nil {
|
||||
if err != nil {
|
||||
assert.Equal(t, tc.expectedErr.Error(), err.Error())
|
||||
} else {
|
||||
t.Errorf("Expected error %v but got nil", tc.expectedErr)
|
||||
}
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppAwareCache(t *testing.T) {
|
||||
_, db, cluster1, cluster2, cluster3, cluster4, cluster5 := createTestClusters()
|
||||
_, app1, app2, app3, app4, app5 := createTestApps()
|
||||
|
||||
clusterSharding := NewClusterSharding(db, 0, 1, "legacy")
|
||||
|
||||
clusterList := &v1alpha1.ClusterList{Items: []v1alpha1.Cluster{cluster1, cluster2, cluster3, cluster4, cluster5}}
|
||||
appList := &v1alpha1.ApplicationList{Items: []v1alpha1.Application{app1, app2, app3, app4, app5}}
|
||||
clusterSharding.Init(clusterList, appList)
|
||||
|
||||
appDistribution := clusterSharding.GetAppDistribution()
|
||||
|
||||
assert.Equal(t, 2, appDistribution["cluster1"])
|
||||
assert.Equal(t, 2, appDistribution["cluster2"])
|
||||
assert.Equal(t, 1, appDistribution["cluster3"])
|
||||
|
||||
app6 := createApp("app6", "cluster4")
|
||||
clusterSharding.AddApp(&app6)
|
||||
|
||||
app1Update := createApp("app1", "cluster2")
|
||||
clusterSharding.UpdateApp(&app1Update)
|
||||
|
||||
clusterSharding.DeleteApp(&app3)
|
||||
|
||||
appDistribution = clusterSharding.GetAppDistribution()
|
||||
|
||||
assert.Equal(t, 1, appDistribution["cluster1"])
|
||||
assert.Equal(t, 2, appDistribution["cluster2"])
|
||||
assert.Equal(t, 1, appDistribution["cluster3"])
|
||||
assert.Equal(t, 1, appDistribution["cluster4"])
|
||||
}
|
||||
|
||||
func createTestApps() (appAccessor, v1alpha1.Application, v1alpha1.Application, v1alpha1.Application, v1alpha1.Application, v1alpha1.Application) {
|
||||
app1 := createApp("app1", "cluster1")
|
||||
app2 := createApp("app2", "cluster1")
|
||||
app3 := createApp("app3", "cluster2")
|
||||
app4 := createApp("app4", "cluster2")
|
||||
app5 := createApp("app5", "cluster3")
|
||||
|
||||
apps := []v1alpha1.Application{app1, app2, app3, app4, app5}
|
||||
|
||||
return getAppAccessor(apps), app1, app2, app3, app4, app5
|
||||
}
|
||||
|
||||
func getAppAccessor(apps []v1alpha1.Application) appAccessor {
|
||||
// Convert the array to a slice of pointers
|
||||
appPointers := getAppPointers(apps)
|
||||
appAccessor := func() []*v1alpha1.Application { return appPointers }
|
||||
return appAccessor
|
||||
}
|
||||
|
||||
func getAppPointers(apps []v1alpha1.Application) []*v1alpha1.Application {
|
||||
var appPointers []*v1alpha1.Application
|
||||
for i := range apps {
|
||||
appPointers = append(appPointers, &apps[i])
|
||||
}
|
||||
return appPointers
|
||||
}
|
||||
|
||||
func createApp(name string, server string) v1alpha1.Application {
|
||||
var testApp = `
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ` + name + `
|
||||
spec:
|
||||
destination:
|
||||
server: ` + server + `
|
||||
`
|
||||
|
||||
var app v1alpha1.Application
|
||||
err := yaml.Unmarshal([]byte(testApp), &app)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package sharding
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
@@ -22,9 +23,11 @@ func TestLargeShuffle(t *testing.T) {
|
||||
clusterList.Items = append(clusterList.Items, cluster)
|
||||
}
|
||||
db.On("ListClusters", mock.Anything).Return(clusterList, nil)
|
||||
clusterAccessor := getClusterAccessor(clusterList.Items)
|
||||
// Test with replicas set to 256
|
||||
t.Setenv(common.EnvControllerReplicas, "256")
|
||||
distributionFunction := RoundRobinDistributionFunction(&db)
|
||||
replicasCount := 256
|
||||
t.Setenv(common.EnvControllerReplicas, strconv.Itoa(replicasCount))
|
||||
distributionFunction := RoundRobinDistributionFunction(clusterAccessor, replicasCount)
|
||||
for i, c := range clusterList.Items {
|
||||
assert.Equal(t, i%2567, distributionFunction(&c))
|
||||
}
|
||||
@@ -44,10 +47,11 @@ func TestShuffle(t *testing.T) {
|
||||
|
||||
clusterList := &v1alpha1.ClusterList{Items: []v1alpha1.Cluster{cluster1, cluster2, cluster3, cluster4, cluster5, cluster6}}
|
||||
db.On("ListClusters", mock.Anything).Return(clusterList, nil)
|
||||
|
||||
clusterAccessor := getClusterAccessor(clusterList.Items)
|
||||
// Test with replicas set to 3
|
||||
t.Setenv(common.EnvControllerReplicas, "3")
|
||||
distributionFunction := RoundRobinDistributionFunction(&db)
|
||||
replicasCount := 3
|
||||
distributionFunction := RoundRobinDistributionFunction(clusterAccessor, replicasCount)
|
||||
assert.Equal(t, 0, distributionFunction(nil))
|
||||
assert.Equal(t, 0, distributionFunction(&cluster1))
|
||||
assert.Equal(t, 1, distributionFunction(&cluster2))
|
||||
|
||||
@@ -33,8 +33,10 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/util/app/path"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
argodiff "github.com/argoproj/argo-cd/v2/util/argo/diff"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
|
||||
"github.com/argoproj/argo-cd/v2/util/db"
|
||||
"github.com/argoproj/argo-cd/v2/util/gpg"
|
||||
@@ -117,6 +119,7 @@ type appStateManager struct {
|
||||
repoErrorCache goSync.Map
|
||||
repoErrorGracePeriod time.Duration
|
||||
serverSideDiff bool
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
|
||||
}
|
||||
|
||||
// GetRepoObjs will generate the manifests for the given application delegating the
|
||||
@@ -194,6 +197,38 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp
|
||||
return nil, nil, fmt.Errorf("failed to get Kustomize options for source %d of %d: %w", i+1, len(sources), err)
|
||||
}
|
||||
|
||||
syncedRevision := app.Status.Sync.Revision
|
||||
if app.Spec.HasMultipleSources() {
|
||||
if i < len(app.Status.Sync.Revisions) {
|
||||
syncedRevision = app.Status.Sync.Revisions[i]
|
||||
} else {
|
||||
syncedRevision = ""
|
||||
}
|
||||
}
|
||||
|
||||
val, ok := app.Annotations[v1alpha1.AnnotationKeyManifestGeneratePaths]
|
||||
if !source.IsHelm() && syncedRevision != "" && ok && val != "" {
|
||||
// Validate the manifest-generate-path annotation to avoid generating manifests if it has not changed.
|
||||
_, err = repoClient.UpdateRevisionForPaths(context.Background(), &apiclient.UpdateRevisionForPathsRequest{
|
||||
Repo: repo,
|
||||
Revision: revisions[i],
|
||||
SyncedRevision: syncedRevision,
|
||||
Paths: path.GetAppRefreshPaths(app),
|
||||
AppLabelKey: appLabelKey,
|
||||
AppName: app.InstanceName(m.namespace),
|
||||
Namespace: app.Spec.Destination.Namespace,
|
||||
ApplicationSource: &source,
|
||||
KubeVersion: serverVersion,
|
||||
ApiVersions: argo.APIResourcesToStrings(apiResources, true),
|
||||
TrackingMethod: string(argo.GetTrackingMethod(m.settingsMgr)),
|
||||
RefSources: refSources,
|
||||
HasMultipleSources: app.Spec.HasMultipleSources(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to compare revisions for source %d of %d: %w", i+1, len(sources), err)
|
||||
}
|
||||
}
|
||||
|
||||
ts.AddCheckpoint("version_ms")
|
||||
log.Debugf("Generating Manifest for source %s revision %s", source, revisions[i])
|
||||
manifestInfo, err := repoClient.GenerateManifest(context.Background(), &apiclient.ManifestRequest{
|
||||
@@ -605,7 +640,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
|
||||
useDiffCache := useDiffCache(noCache, manifestInfos, sources, app, manifestRevisions, m.statusRefreshTimeout, serverSideDiff, logCtx)
|
||||
|
||||
diffConfigBuilder := argodiff.NewDiffConfigBuilder().
|
||||
WithDiffSettings(app.Spec.IgnoreDifferences, resourceOverrides, compareOptions.IgnoreAggregatedRoles).
|
||||
WithDiffSettings(app.Spec.IgnoreDifferences, resourceOverrides, compareOptions.IgnoreAggregatedRoles, m.ignoreNormalizerOpts).
|
||||
WithTracking(appLabelKey, string(trackingMethod))
|
||||
|
||||
if useDiffCache {
|
||||
@@ -880,7 +915,16 @@ func useDiffCache(noCache bool, manifestInfos []*apiclient.ManifestResponse, sou
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *appStateManager) persistRevisionHistory(app *v1alpha1.Application, revision string, source v1alpha1.ApplicationSource, revisions []string, sources []v1alpha1.ApplicationSource, hasMultipleSources bool, startedAt metav1.Time) error {
|
||||
func (m *appStateManager) persistRevisionHistory(
|
||||
app *v1alpha1.Application,
|
||||
revision string,
|
||||
source v1alpha1.ApplicationSource,
|
||||
revisions []string,
|
||||
sources []v1alpha1.ApplicationSource,
|
||||
hasMultipleSources bool,
|
||||
startedAt metav1.Time,
|
||||
initiatedBy v1alpha1.OperationInitiator,
|
||||
) error {
|
||||
var nextID int64
|
||||
if len(app.Status.History) > 0 {
|
||||
nextID = app.Status.History.LastRevisionHistory().ID + 1
|
||||
@@ -893,6 +937,7 @@ func (m *appStateManager) persistRevisionHistory(app *v1alpha1.Application, revi
|
||||
ID: nextID,
|
||||
Sources: sources,
|
||||
Revisions: revisions,
|
||||
InitiatedBy: initiatedBy,
|
||||
})
|
||||
} else {
|
||||
app.Status.History = append(app.Status.History, v1alpha1.RevisionHistory{
|
||||
@@ -901,6 +946,7 @@ func (m *appStateManager) persistRevisionHistory(app *v1alpha1.Application, revi
|
||||
DeployStartedAt: &startedAt,
|
||||
ID: nextID,
|
||||
Source: source,
|
||||
InitiatedBy: initiatedBy,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -935,6 +981,7 @@ func NewAppStateManager(
|
||||
persistResourceHealth bool,
|
||||
repoErrorGracePeriod time.Duration,
|
||||
serverSideDiff bool,
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts,
|
||||
) AppStateManager {
|
||||
return &appStateManager{
|
||||
liveStateCache: liveStateCache,
|
||||
@@ -952,6 +999,7 @@ func NewAppStateManager(
|
||||
persistResourceHealth: persistResourceHealth,
|
||||
repoErrorGracePeriod: repoErrorGracePeriod,
|
||||
serverSideDiff: serverSideDiff,
|
||||
ignoreNormalizerOpts: ignoreNormalizerOpts,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,11 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/controller/testdata"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
mockrepoclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
|
||||
"github.com/argoproj/argo-cd/v2/test"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
)
|
||||
@@ -649,6 +652,37 @@ var defaultProj = argoappv1.AppProject{
|
||||
},
|
||||
}
|
||||
|
||||
// TestCompareAppStateWithManifestGeneratePath tests that it compares revisions when the manifest-generate-path annotation is set.
|
||||
func TestCompareAppStateWithManifestGeneratePath(t *testing.T) {
|
||||
app := newFakeApp()
|
||||
app.SetAnnotations(map[string]string{argoappv1.AnnotationKeyManifestGeneratePaths: "."})
|
||||
app.Status.Sync = argoappv1.SyncStatus{
|
||||
Revision: "abc123",
|
||||
Status: argoappv1.SyncStatusCodeSynced,
|
||||
}
|
||||
|
||||
data := fakeData{
|
||||
manifestResponse: &apiclient.ManifestResponse{
|
||||
Manifests: []string{},
|
||||
Namespace: test.FakeDestNamespace,
|
||||
Server: test.FakeClusterURL,
|
||||
Revision: "abc123",
|
||||
},
|
||||
updateRevisionForPathsResponse: &apiclient.UpdateRevisionForPathsResponse{},
|
||||
}
|
||||
|
||||
ctrl := newFakeController(&data, nil)
|
||||
revisions := make([]string, 0)
|
||||
revisions = append(revisions, "abc123")
|
||||
compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, app.Spec.GetSources(), false, false, nil, false)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, compRes)
|
||||
assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
|
||||
assert.Equal(t, "abc123", compRes.syncStatus.Revision)
|
||||
ctrl.repoClientset.(*mockrepoclient.Clientset).RepoServerServiceClient.(*mockrepoclient.RepoServerServiceClient).AssertNumberOfCalls(t, "UpdateRevisionForPaths", 1)
|
||||
}
|
||||
|
||||
func TestSetHealth(t *testing.T) {
|
||||
app := newFakeApp()
|
||||
deployment := kube.MustToUnstructured(&v1.Deployment{
|
||||
@@ -838,7 +872,7 @@ func Test_appStateManager_persistRevisionHistory(t *testing.T) {
|
||||
app.Spec.RevisionHistoryLimit = &i
|
||||
}
|
||||
addHistory := func() {
|
||||
err := manager.persistRevisionHistory(app, "my-revision", argoappv1.ApplicationSource{}, []string{}, []argoappv1.ApplicationSource{}, false, metav1.Time{})
|
||||
err := manager.persistRevisionHistory(app, "my-revision", argoappv1.ApplicationSource{}, []string{}, []argoappv1.ApplicationSource{}, false, metav1.Time{}, v1alpha1.OperationInitiator{})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
addHistory()
|
||||
@@ -874,7 +908,7 @@ func Test_appStateManager_persistRevisionHistory(t *testing.T) {
|
||||
assert.Len(t, app.Status.History, 9)
|
||||
|
||||
metav1NowTime := metav1.NewTime(time.Now())
|
||||
err := manager.persistRevisionHistory(app, "my-revision", argoappv1.ApplicationSource{}, []string{}, []argoappv1.ApplicationSource{}, false, metav1NowTime)
|
||||
err := manager.persistRevisionHistory(app, "my-revision", argoappv1.ApplicationSource{}, []string{}, []argoappv1.ApplicationSource{}, false, metav1NowTime, v1alpha1.OperationInitiator{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, app.Status.History.LastRevisionHistory().DeployStartedAt, &metav1NowTime)
|
||||
}
|
||||
@@ -1508,6 +1542,17 @@ func TestUseDiffCache(t *testing.T) {
|
||||
expectedUseCache: true,
|
||||
serverSideDiff: false,
|
||||
},
|
||||
{
|
||||
testName: "will use diff cache with sync policy",
|
||||
noCache: false,
|
||||
manifestInfos: manifestInfos("rev1"),
|
||||
sources: sources(),
|
||||
app: test.YamlToApplication(testdata.DiffCacheYaml),
|
||||
manifestRevisions: []string{"rev1"},
|
||||
statusRefreshTimeout: time.Hour * 24,
|
||||
expectedUseCache: true,
|
||||
serverSideDiff: true,
|
||||
},
|
||||
{
|
||||
testName: "will use diff cache for multisource",
|
||||
noCache: false,
|
||||
|
||||
@@ -2,7 +2,6 @@ package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -11,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
cdcommon "github.com/argoproj/argo-cd/v2/common"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/sync"
|
||||
"github.com/argoproj/gitops-engine/pkg/sync/common"
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/managedfields"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/kubectl/pkg/util/openapi"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/controller/metrics"
|
||||
@@ -103,7 +104,7 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
|
||||
if syncOp.SyncOptions.HasOption("FailOnSharedResource=true") &&
|
||||
hasSharedResource {
|
||||
state.Phase = common.OperationFailed
|
||||
state.Message = fmt.Sprintf("Shared resouce found: %s", sharedResourceMessage)
|
||||
state.Message = fmt.Sprintf("Shared resource found: %s", sharedResourceMessage)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -161,6 +162,12 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
|
||||
state.Phase = common.OperationError
|
||||
state.Message = fmt.Sprintf("Failed to load application project: %v", err)
|
||||
return
|
||||
} else if syncWindowPreventsSync(app, proj) {
|
||||
// If the operation is currently running, simply let the user know the sync is blocked by a current sync window
|
||||
if state.Phase == common.OperationRunning {
|
||||
state.Message = "Sync operation blocked by sync window"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if app.Spec.HasMultipleSources() {
|
||||
@@ -391,7 +398,7 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
|
||||
logEntry.WithField("duration", time.Since(start)).Info("sync/terminate complete")
|
||||
|
||||
if !syncOp.DryRun && len(syncOp.Resources) == 0 && state.Phase.Successful() {
|
||||
err := m.persistRevisionHistory(app, compareResult.syncStatus.Revision, source, compareResult.syncStatus.Revisions, compareResult.syncStatus.ComparedTo.Sources, app.Spec.HasMultipleSources(), state.StartedAt)
|
||||
err := m.persistRevisionHistory(app, compareResult.syncStatus.Revision, source, compareResult.syncStatus.Revisions, compareResult.syncStatus.ComparedTo.Sources, app.Spec.HasMultipleSources(), state.StartedAt, state.Operation.InitiatedBy)
|
||||
if err != nil {
|
||||
state.Phase = common.OperationError
|
||||
state.Message = fmt.Sprintf("failed to record sync to history: %v", err)
|
||||
@@ -399,11 +406,10 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
|
||||
}
|
||||
}
|
||||
|
||||
// normalizeTargetResources will apply the diff normalization in all live and target resources.
|
||||
// Then it calculates the merge patch between the normalized live and the current live resources.
|
||||
// Finally it applies the merge patch in the normalized target resources. This is done to ensure
|
||||
// that target resources have the same ignored diff fields values from live ones to avoid them to
|
||||
// be applied in the cluster. Returns the list of normalized target resources.
|
||||
// normalizeTargetResources modifies target resources to ensure ignored fields are not touched during synchronization:
|
||||
// - applies normalization to the target resources based on the live resources
|
||||
// - copies ignored fields from the matching live resources: apply normalizer to the live resource,
|
||||
// calculates the patch performed by normalizer and applies the patch to the target resource
|
||||
func normalizeTargetResources(cr *comparisonResult) ([]*unstructured.Unstructured, error) {
|
||||
// normalize live and target resources
|
||||
normalized, err := diff.Normalize(cr.reconciliationResult.Live, cr.reconciliationResult.Target, cr.diffConfig)
|
||||
@@ -422,94 +428,35 @@ func normalizeTargetResources(cr *comparisonResult) ([]*unstructured.Unstructure
|
||||
patchedTargets = append(patchedTargets, originalTarget)
|
||||
continue
|
||||
}
|
||||
// calculate targetPatch between normalized and target resource
|
||||
targetPatch, err := getMergePatch(normalizedTarget, originalTarget)
|
||||
|
||||
var lookupPatchMeta *strategicpatch.PatchMetaFromStruct
|
||||
versionedObject, err := scheme.Scheme.New(normalizedTarget.GroupVersionKind())
|
||||
if err == nil {
|
||||
meta, err := strategicpatch.NewPatchMetaFromStruct(versionedObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lookupPatchMeta = &meta
|
||||
}
|
||||
|
||||
livePatch, err := getMergePatch(normalized.Lives[idx], live, lookupPatchMeta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check if there is a patch to apply. An empty patch is identified by a '{}' string.
|
||||
if len(targetPatch) > 2 {
|
||||
livePatch, err := getMergePatch(normalized.Lives[idx], live)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// generate a minimal patch that uses the fields from targetPatch (template)
|
||||
// with livePatch values
|
||||
patch, err := compilePatch(targetPatch, livePatch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
normalizedTarget, err = applyMergePatch(normalizedTarget, patch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// if there is no patch just use the original target
|
||||
normalizedTarget = originalTarget
|
||||
normalizedTarget, err = applyMergePatch(normalizedTarget, livePatch, versionedObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
patchedTargets = append(patchedTargets, normalizedTarget)
|
||||
}
|
||||
return patchedTargets, nil
|
||||
}
|
||||
|
||||
// compilePatch will generate a patch using the fields from templatePatch with
|
||||
// the values from valuePatch.
|
||||
func compilePatch(templatePatch, valuePatch []byte) ([]byte, error) {
|
||||
templateMap := make(map[string]interface{})
|
||||
err := json.Unmarshal(templatePatch, &templateMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
valueMap := make(map[string]interface{})
|
||||
err = json.Unmarshal(valuePatch, &valueMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resultMap := intersectMap(templateMap, valueMap)
|
||||
return json.Marshal(resultMap)
|
||||
}
|
||||
|
||||
// intersectMap will return map with the fields intersection from the 2 provided
|
||||
// maps populated with the valueMap values.
|
||||
func intersectMap(templateMap, valueMap map[string]interface{}) map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
for k, v := range templateMap {
|
||||
if innerTMap, ok := v.(map[string]interface{}); ok {
|
||||
if innerVMap, ok := valueMap[k].(map[string]interface{}); ok {
|
||||
result[k] = intersectMap(innerTMap, innerVMap)
|
||||
}
|
||||
} else if innerTSlice, ok := v.([]interface{}); ok {
|
||||
if innerVSlice, ok := valueMap[k].([]interface{}); ok {
|
||||
items := []interface{}{}
|
||||
for idx, innerTSliceValue := range innerTSlice {
|
||||
if idx < len(innerVSlice) {
|
||||
if tSliceValueMap, ok := innerTSliceValue.(map[string]interface{}); ok {
|
||||
if vSliceValueMap, ok := innerVSlice[idx].(map[string]interface{}); ok {
|
||||
item := intersectMap(tSliceValueMap, vSliceValueMap)
|
||||
items = append(items, item)
|
||||
}
|
||||
} else {
|
||||
items = append(items, innerVSlice[idx])
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(items) > 0 {
|
||||
result[k] = items
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, ok := valueMap[k]; ok {
|
||||
result[k] = valueMap[k]
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// getMergePatch calculates and returns the patch between the original and the
|
||||
// modified unstructures.
|
||||
func getMergePatch(original, modified *unstructured.Unstructured) ([]byte, error) {
|
||||
func getMergePatch(original, modified *unstructured.Unstructured, lookupPatchMeta *strategicpatch.PatchMetaFromStruct) ([]byte, error) {
|
||||
originalJSON, err := original.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -518,20 +465,30 @@ func getMergePatch(original, modified *unstructured.Unstructured) ([]byte, error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lookupPatchMeta != nil {
|
||||
return strategicpatch.CreateThreeWayMergePatch(modifiedJSON, modifiedJSON, originalJSON, lookupPatchMeta, true)
|
||||
}
|
||||
|
||||
return jsonpatch.CreateMergePatch(originalJSON, modifiedJSON)
|
||||
}
|
||||
|
||||
// applyMergePatch will apply the given patch in the obj and return the patched
|
||||
// unstructure.
|
||||
func applyMergePatch(obj *unstructured.Unstructured, patch []byte) (*unstructured.Unstructured, error) {
|
||||
func applyMergePatch(obj *unstructured.Unstructured, patch []byte, versionedObject interface{}) (*unstructured.Unstructured, error) {
|
||||
originalJSON, err := obj.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
patchedJSON, err := jsonpatch.MergePatch(originalJSON, patch)
|
||||
var patchedJSON []byte
|
||||
if versionedObject == nil {
|
||||
patchedJSON, err = jsonpatch.MergePatch(originalJSON, patch)
|
||||
} else {
|
||||
patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, patch, versionedObject)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
patchedObj := &unstructured.Unstructured{}
|
||||
_, _, err = unstructured.UnstructuredJSONScheme.Decode(patchedJSON, nil, patchedObj)
|
||||
if err != nil {
|
||||
@@ -573,3 +530,12 @@ func delayBetweenSyncWaves(phase common.SyncPhase, wave int, finalWave bool) err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func syncWindowPreventsSync(app *v1alpha1.Application, proj *v1alpha1.AppProject) bool {
|
||||
window := proj.Spec.SyncWindows.Matches(app)
|
||||
isManual := false
|
||||
if app.Status.OperationState != nil {
|
||||
isManual = !app.Status.OperationState.Operation.InitiatedBy.Automated
|
||||
}
|
||||
return !window.CanSync(isManual)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/test"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/diff"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
)
|
||||
|
||||
func TestPersistRevisionHistory(t *testing.T) {
|
||||
@@ -254,6 +255,75 @@ func TestAppStateManager_SyncAppState(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestSyncWindowDeniesSync(t *testing.T) {
|
||||
type fixture struct {
|
||||
project *v1alpha1.AppProject
|
||||
application *v1alpha1.Application
|
||||
controller *ApplicationController
|
||||
}
|
||||
|
||||
setup := func() *fixture {
|
||||
app := newFakeApp()
|
||||
app.Status.OperationState = nil
|
||||
app.Status.History = nil
|
||||
|
||||
project := &v1alpha1.AppProject{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: test.FakeArgoCDNamespace,
|
||||
Name: "default",
|
||||
},
|
||||
Spec: v1alpha1.AppProjectSpec{
|
||||
SyncWindows: v1alpha1.SyncWindows{{
|
||||
Kind: "deny",
|
||||
Schedule: "0 0 * * *",
|
||||
Duration: "24h",
|
||||
Clusters: []string{"*"},
|
||||
Namespaces: []string{"*"},
|
||||
Applications: []string{"*"},
|
||||
}},
|
||||
},
|
||||
}
|
||||
data := fakeData{
|
||||
apps: []runtime.Object{app, project},
|
||||
manifestResponse: &apiclient.ManifestResponse{
|
||||
Manifests: []string{},
|
||||
Namespace: test.FakeDestNamespace,
|
||||
Server: test.FakeClusterURL,
|
||||
Revision: "abc123",
|
||||
},
|
||||
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
|
||||
}
|
||||
ctrl := newFakeController(&data, nil)
|
||||
|
||||
return &fixture{
|
||||
project: project,
|
||||
application: app,
|
||||
controller: ctrl,
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("will keep the sync progressing if a sync window prevents the sync", func(t *testing.T) {
|
||||
// given a project with an active deny sync window and an operation in progress
|
||||
t.Parallel()
|
||||
f := setup()
|
||||
opMessage := "Sync operation blocked by sync window"
|
||||
|
||||
opState := &v1alpha1.OperationState{Operation: v1alpha1.Operation{
|
||||
Sync: &v1alpha1.SyncOperation{
|
||||
Source: &v1alpha1.ApplicationSource{},
|
||||
}},
|
||||
Phase: common.OperationRunning,
|
||||
}
|
||||
// when
|
||||
f.controller.appStateManager.SyncAppState(f.application, opState)
|
||||
|
||||
//then
|
||||
assert.Equal(t, common.OperationRunning, opState.Phase)
|
||||
assert.Contains(t, opState.Message, opMessage)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestNormalizeTargetResources(t *testing.T) {
|
||||
type fixture struct {
|
||||
comparisonResult *comparisonResult
|
||||
@@ -261,7 +331,7 @@ func TestNormalizeTargetResources(t *testing.T) {
|
||||
setup := func(t *testing.T, ignores []v1alpha1.ResourceIgnoreDifferences) *fixture {
|
||||
t.Helper()
|
||||
dc, err := diff.NewDiffConfigBuilder().
|
||||
WithDiffSettings(ignores, nil, true).
|
||||
WithDiffSettings(ignores, nil, true, normalizers.IgnoreNormalizerOpts{}).
|
||||
WithNoCache().
|
||||
Build()
|
||||
require.NoError(t, err)
|
||||
@@ -386,3 +456,207 @@ func TestNormalizeTargetResources(t *testing.T) {
|
||||
assert.Equal(t, 2, len(containers))
|
||||
})
|
||||
}
|
||||
|
||||
func TestNormalizeTargetResourcesWithList(t *testing.T) {
|
||||
type fixture struct {
|
||||
comparisonResult *comparisonResult
|
||||
}
|
||||
setupHttpProxy := func(t *testing.T, ignores []v1alpha1.ResourceIgnoreDifferences) *fixture {
|
||||
t.Helper()
|
||||
dc, err := diff.NewDiffConfigBuilder().
|
||||
WithDiffSettings(ignores, nil, true, normalizers.IgnoreNormalizerOpts{}).
|
||||
WithNoCache().
|
||||
Build()
|
||||
require.NoError(t, err)
|
||||
live := test.YamlToUnstructured(testdata.LiveHTTPProxy)
|
||||
target := test.YamlToUnstructured(testdata.TargetHTTPProxy)
|
||||
return &fixture{
|
||||
&comparisonResult{
|
||||
reconciliationResult: sync.ReconciliationResult{
|
||||
Live: []*unstructured.Unstructured{live},
|
||||
Target: []*unstructured.Unstructured{target},
|
||||
},
|
||||
diffConfig: dc,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("will properly ignore nested fields within arrays", func(t *testing.T) {
|
||||
// given
|
||||
ignores := []v1alpha1.ResourceIgnoreDifferences{
|
||||
{
|
||||
Group: "projectcontour.io",
|
||||
Kind: "HTTPProxy",
|
||||
JQPathExpressions: []string{".spec.routes[]"},
|
||||
//JSONPointers: []string{"/spec/routes"},
|
||||
},
|
||||
}
|
||||
f := setupHttpProxy(t, ignores)
|
||||
target := test.YamlToUnstructured(testdata.TargetHTTPProxy)
|
||||
f.comparisonResult.reconciliationResult.Target = []*unstructured.Unstructured{target}
|
||||
|
||||
// when
|
||||
patchedTargets, err := normalizeTargetResources(f.comparisonResult)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(f.comparisonResult.reconciliationResult.Live))
|
||||
require.Equal(t, 1, len(f.comparisonResult.reconciliationResult.Target))
|
||||
require.Equal(t, 1, len(patchedTargets))
|
||||
|
||||
// live should have 1 entry
|
||||
require.Equal(t, 1, len(dig[[]any](f.comparisonResult.reconciliationResult.Live[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors"})))
|
||||
// assert some arbitrary field to show `entries[0]` is not an empty object
|
||||
require.Equal(t, "sample-header", dig[string](f.comparisonResult.reconciliationResult.Live[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors", 0, "entries", 0, "requestHeader", "headerName"}))
|
||||
|
||||
// target has 2 entries
|
||||
require.Equal(t, 2, len(dig[[]any](f.comparisonResult.reconciliationResult.Target[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors", 0, "entries"})))
|
||||
// assert some arbitrary field to show `entries[0]` is not an empty object
|
||||
require.Equal(t, "sample-header", dig[string](f.comparisonResult.reconciliationResult.Target[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors", 0, "entries", 0, "requestHeaderValueMatch", "headers", 0, "name"}))
|
||||
|
||||
// It should be *1* entries in the array
|
||||
require.Equal(t, 1, len(dig[[]any](patchedTargets[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors"})))
|
||||
// and it should NOT equal an empty object
|
||||
require.Len(t, dig[any](patchedTargets[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors", 0, "entries", 0}), 1)
|
||||
|
||||
})
|
||||
t.Run("will correctly set array entries if new entries have been added", func(t *testing.T) {
|
||||
// given
|
||||
ignores := []v1alpha1.ResourceIgnoreDifferences{
|
||||
{
|
||||
Group: "apps",
|
||||
Kind: "Deployment",
|
||||
JQPathExpressions: []string{".spec.template.spec.containers[].env[] | select(.name == \"SOME_ENV_VAR\")"},
|
||||
},
|
||||
}
|
||||
f := setupHttpProxy(t, ignores)
|
||||
live := test.YamlToUnstructured(testdata.LiveDeploymentEnvVarsYaml)
|
||||
target := test.YamlToUnstructured(testdata.TargetDeploymentEnvVarsYaml)
|
||||
f.comparisonResult.reconciliationResult.Live = []*unstructured.Unstructured{live}
|
||||
f.comparisonResult.reconciliationResult.Target = []*unstructured.Unstructured{target}
|
||||
|
||||
// when
|
||||
targets, err := normalizeTargetResources(f.comparisonResult)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(targets))
|
||||
containers, ok, err := unstructured.NestedSlice(targets[0].Object, "spec", "template", "spec", "containers")
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, 1, len(containers))
|
||||
|
||||
ports := containers[0].(map[string]interface{})["ports"].([]interface{})
|
||||
assert.Equal(t, 1, len(ports))
|
||||
|
||||
env := containers[0].(map[string]interface{})["env"].([]interface{})
|
||||
assert.Equal(t, 3, len(env))
|
||||
|
||||
first := env[0]
|
||||
second := env[1]
|
||||
third := env[2]
|
||||
|
||||
// Currently the defined order at this time is the insertion order of the target manifest.
|
||||
assert.Equal(t, "SOME_ENV_VAR", first.(map[string]interface{})["name"])
|
||||
assert.Equal(t, "some_value", first.(map[string]interface{})["value"])
|
||||
|
||||
assert.Equal(t, "SOME_OTHER_ENV_VAR", second.(map[string]interface{})["name"])
|
||||
assert.Equal(t, "some_other_value", second.(map[string]interface{})["value"])
|
||||
|
||||
assert.Equal(t, "YET_ANOTHER_ENV_VAR", third.(map[string]interface{})["name"])
|
||||
assert.Equal(t, "yet_another_value", third.(map[string]interface{})["value"])
|
||||
})
|
||||
|
||||
t.Run("ignore-deployment-image-replicas-changes-additive", func(t *testing.T) {
|
||||
// given
|
||||
|
||||
ignores := []v1alpha1.ResourceIgnoreDifferences{
|
||||
{
|
||||
Group: "apps",
|
||||
Kind: "Deployment",
|
||||
JSONPointers: []string{"/spec/replicas"},
|
||||
}, {
|
||||
Group: "apps",
|
||||
Kind: "Deployment",
|
||||
JQPathExpressions: []string{".spec.template.spec.containers[].image"},
|
||||
},
|
||||
}
|
||||
f := setupHttpProxy(t, ignores)
|
||||
live := test.YamlToUnstructured(testdata.MinimalImageReplicaDeploymentYaml)
|
||||
target := test.YamlToUnstructured(testdata.AdditionalImageReplicaDeploymentYaml)
|
||||
f.comparisonResult.reconciliationResult.Live = []*unstructured.Unstructured{live}
|
||||
f.comparisonResult.reconciliationResult.Target = []*unstructured.Unstructured{target}
|
||||
|
||||
// when
|
||||
targets, err := normalizeTargetResources(f.comparisonResult)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(targets))
|
||||
metadata, ok, err := unstructured.NestedMap(targets[0].Object, "metadata")
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
labels, ok := metadata["labels"].(map[string]interface{})
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, 2, len(labels))
|
||||
assert.Equal(t, "web", labels["appProcess"])
|
||||
|
||||
spec, ok, err := unstructured.NestedMap(targets[0].Object, "spec")
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, int64(1), spec["replicas"])
|
||||
|
||||
template, ok := spec["template"].(map[string]interface{})
|
||||
require.True(t, ok)
|
||||
|
||||
tMetadata, ok := template["metadata"].(map[string]interface{})
|
||||
require.True(t, ok)
|
||||
tLabels, ok := tMetadata["labels"].(map[string]interface{})
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, 2, len(tLabels))
|
||||
assert.Equal(t, "web", tLabels["appProcess"])
|
||||
|
||||
tSpec, ok := template["spec"].(map[string]interface{})
|
||||
require.True(t, ok)
|
||||
containers, ok, err := unstructured.NestedSlice(tSpec, "containers")
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, 1, len(containers))
|
||||
|
||||
first := containers[0].(map[string]interface{})
|
||||
assert.Equal(t, "alpine:3", first["image"])
|
||||
|
||||
resources, ok := first["resources"].(map[string]interface{})
|
||||
require.True(t, ok)
|
||||
requests, ok := resources["requests"].(map[string]interface{})
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "400m", requests["cpu"])
|
||||
|
||||
env, ok, err := unstructured.NestedSlice(first, "env")
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, 1, len(env))
|
||||
|
||||
env0 := env[0].(map[string]interface{})
|
||||
assert.Equal(t, "EV", env0["name"])
|
||||
assert.Equal(t, "here", env0["value"])
|
||||
})
|
||||
}
|
||||
|
||||
func dig[T any](obj interface{}, path []interface{}) T {
|
||||
i := obj
|
||||
|
||||
for _, segment := range path {
|
||||
switch segment.(type) {
|
||||
case int:
|
||||
i = i.([]interface{})[segment.(int)]
|
||||
case string:
|
||||
i = i.(map[string]interface{})[segment.(string)]
|
||||
default:
|
||||
panic("invalid path for object")
|
||||
}
|
||||
}
|
||||
|
||||
return i.(T)
|
||||
}
|
||||
|
||||
28
controller/testdata/additional-image-replicas-deployment.yaml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: client
|
||||
appProcess: web
|
||||
name: client
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: client
|
||||
strategy: {}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: client
|
||||
appProcess: web
|
||||
spec:
|
||||
containers:
|
||||
- image: alpine:2
|
||||
name: alpine
|
||||
resources:
|
||||
requests:
|
||||
cpu: 400m
|
||||
env:
|
||||
- name: EV
|
||||
value: here
|
||||
21
controller/testdata/data.go
vendored
@@ -11,4 +11,25 @@ var (
|
||||
|
||||
//go:embed target-deployment-new-entries.yaml
|
||||
TargetDeploymentNewEntries string
|
||||
|
||||
//go:embed diff-cache.yaml
|
||||
DiffCacheYaml string
|
||||
|
||||
//go:embed live-httpproxy.yaml
|
||||
LiveHTTPProxy string
|
||||
|
||||
//go:embed target-httpproxy.yaml
|
||||
TargetHTTPProxy string
|
||||
|
||||
//go:embed live-deployment-env-vars.yaml
|
||||
LiveDeploymentEnvVarsYaml string
|
||||
|
||||
//go:embed target-deployment-env-vars.yaml
|
||||
TargetDeploymentEnvVarsYaml string
|
||||
|
||||
//go:embed minimal-image-replicas-deployment.yaml
|
||||
MinimalImageReplicaDeploymentYaml string
|
||||
|
||||
//go:embed additional-image-replicas-deployment.yaml
|
||||
AdditionalImageReplicaDeploymentYaml string
|
||||
)
|
||||
|
||||
498
controller/testdata/diff-cache.yaml
vendored
Normal file
@@ -0,0 +1,498 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
annotations:
|
||||
argocd-image-updater.argoproj.io/allow-tags: any
|
||||
argocd-image-updater.argoproj.io/ignore-tags: ""
|
||||
argocd-image-updater.argoproj.io/image-list-disabled-hack: ""
|
||||
argocd-image-updater.argoproj.io/update-strategy: semver
|
||||
argocd-image-updater.argoproj.io/write-back-method: git
|
||||
argocd-image-updater.argoproj.io/write-back-target: kustomization
|
||||
argocd-notif-onDeployed.slack-disabled: ""
|
||||
argocd-notif-onHealthDegraded.slack-disabled: ""
|
||||
argocd-notif-onSyncFailed.slack-disabled: ""
|
||||
argocd-notif-onSyncRunning.slack-disabled: ""
|
||||
argocd-notif-onSyncStatusUnknown.slack-disabled: ""
|
||||
argocd-notif-onSyncSucceeded.slack-disabled: ""
|
||||
argocd.argoproj.io/compare-options: ServerSideDiff=true
|
||||
argocd.argoproj.io/manifest-generate-paths: .;/chart
|
||||
creationTimestamp: "2024-03-04T21:30:33Z"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
generation: 263
|
||||
labels:
|
||||
cloud_provider: gcp
|
||||
cluster_name: gke-alpha-01-europe-west1
|
||||
foo: bar
|
||||
preview: "true"
|
||||
project: sre
|
||||
service_class: alpha
|
||||
stack: gke-v2
|
||||
name: velero-test
|
||||
namespace: argo-cd
|
||||
ownerReferences:
|
||||
- apiVersion: argoproj.io/v1alpha1
|
||||
blockOwnerDeletion: true
|
||||
controller: true
|
||||
kind: ApplicationSet
|
||||
name: velero
|
||||
uid: 86cdfba4-8697-47b3-8489-71fab7f4a805
|
||||
resourceVersion: "722811357"
|
||||
uid: 94978696-4fd4-40b3-a1de-38d9df9e9316
|
||||
spec:
|
||||
destination:
|
||||
name: gke-alpha-01-europe-west1
|
||||
namespace: test-lla
|
||||
project: sre
|
||||
source:
|
||||
path: instances/test
|
||||
plugin:
|
||||
env:
|
||||
- name: RELEASE_NAME
|
||||
value: test-lla
|
||||
- name: CHART_REPOSITORY
|
||||
value: oci://europe-west1-docker.pkg.dev/platform-89be/charts
|
||||
- name: CHART_NAME
|
||||
value: velero
|
||||
- name: PREVIEW
|
||||
value: "false"
|
||||
- name: HELM_VALUES
|
||||
value: |
|
||||
global:
|
||||
app:
|
||||
cluster_name: gke-alpha-01-europe-west1
|
||||
service_class: alpha
|
||||
cloud_provider: gcp
|
||||
cluster_stack: gke-v2
|
||||
- name: HELM_ARGS
|
||||
value: ""
|
||||
name: cmp-helm-v2
|
||||
repoURL: https://github.com/mirakl/manifests-velero.git
|
||||
targetRevision: test-lla
|
||||
syncPolicy:
|
||||
retry:
|
||||
backoff:
|
||||
duration: 5s
|
||||
factor: 2
|
||||
maxDuration: 3m
|
||||
limit: 10
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ApplyOutOfSyncOnly=true
|
||||
- RespectIgnoreDifferences=false
|
||||
- ServerSideApply=true
|
||||
- Validate=true
|
||||
status:
|
||||
controllerNamespace: argo-cd
|
||||
health:
|
||||
status: Healthy
|
||||
history:
|
||||
- deployStartedAt: "2024-03-04T22:00:05Z"
|
||||
deployedAt: "2024-03-04T22:00:06Z"
|
||||
id: 14
|
||||
revision: ea8759964626a583667a2bfd08f334ec2070040a
|
||||
source:
|
||||
path: instances/test
|
||||
plugin:
|
||||
env:
|
||||
- name: RELEASE_NAME
|
||||
value: test-lla
|
||||
- name: CHART_REPOSITORY
|
||||
value: oci://europe-west1-docker.pkg.dev/platform-89be/charts
|
||||
- name: CHART_NAME
|
||||
value: velero
|
||||
- name: PREVIEW
|
||||
value: "false"
|
||||
- name: HELM_VALUES
|
||||
value: |
|
||||
global:
|
||||
app:
|
||||
cluster_name: gke-alpha-01-europe-west1
|
||||
service_class: alpha
|
||||
cloud_provider: gcp
|
||||
cluster_stack: gke-v2
|
||||
- name: HELM_ARGS
|
||||
value: ""
|
||||
name: cmp-helm-v2
|
||||
repoURL: https://github.com/mirakl/manifests-velero.git
|
||||
targetRevision: test-lla
|
||||
- deployStartedAt: "2024-03-04T22:08:29Z"
|
||||
deployedAt: "2024-03-04T22:08:30Z"
|
||||
id: 15
|
||||
revision: ea8759964626a583667a2bfd08f334ec2070040a
|
||||
source:
|
||||
path: instances/test
|
||||
plugin:
|
||||
env:
|
||||
- name: RELEASE_NAME
|
||||
value: test-lla
|
||||
- name: CHART_REPOSITORY
|
||||
value: oci://europe-west1-docker.pkg.dev/platform-89be/charts
|
||||
- name: CHART_NAME
|
||||
value: velero
|
||||
- name: PREVIEW
|
||||
value: "false"
|
||||
- name: HELM_VALUES
|
||||
value: |
|
||||
global:
|
||||
app:
|
||||
cluster_name: gke-alpha-01-europe-west1
|
||||
service_class: alpha
|
||||
cloud_provider: gcp
|
||||
cluster_stack: gke-v2
|
||||
- name: HELM_ARGS
|
||||
value: ""
|
||||
name: cmp-helm-v2
|
||||
repoURL: https://github.com/mirakl/manifests-velero.git
|
||||
targetRevision: test-lla
|
||||
- deployStartedAt: "2024-03-04T22:09:16Z"
|
||||
deployedAt: "2024-03-04T22:09:16Z"
|
||||
id: 16
|
||||
revision: ea8759964626a583667a2bfd08f334ec2070040a
|
||||
source:
|
||||
path: instances/test
|
||||
plugin:
|
||||
env:
|
||||
- name: RELEASE_NAME
|
||||
value: test-lla
|
||||
- name: CHART_REPOSITORY
|
||||
value: oci://europe-west1-docker.pkg.dev/platform-89be/charts
|
||||
- name: CHART_NAME
|
||||
value: velero
|
||||
- name: PREVIEW
|
||||
value: "false"
|
||||
- name: HELM_VALUES
|
||||
value: |
|
||||
global:
|
||||
app:
|
||||
cluster_name: gke-alpha-01-europe-west1
|
||||
service_class: alpha
|
||||
cloud_provider: gcp
|
||||
cluster_stack: gke-v2
|
||||
- name: HELM_ARGS
|
||||
value: ""
|
||||
name: cmp-helm-v2
|
||||
repoURL: https://github.com/mirakl/manifests-velero.git
|
||||
targetRevision: test-lla
|
||||
- deployStartedAt: "2024-03-04T22:11:41Z"
|
||||
deployedAt: "2024-03-04T22:11:41Z"
|
||||
id: 17
|
||||
revision: ea8759964626a583667a2bfd08f334ec2070040a
|
||||
source:
|
||||
path: instances/test
|
||||
plugin:
|
||||
env:
|
||||
- name: RELEASE_NAME
|
||||
value: test-lla
|
||||
- name: CHART_REPOSITORY
|
||||
value: oci://europe-west1-docker.pkg.dev/platform-89be/charts
|
||||
- name: CHART_NAME
|
||||
value: velero
|
||||
- name: PREVIEW
|
||||
value: "false"
|
||||
- name: HELM_VALUES
|
||||
value: |
|
||||
global:
|
||||
app:
|
||||
cluster_name: gke-alpha-01-europe-west1
|
||||
service_class: alpha
|
||||
cloud_provider: gcp
|
||||
cluster_stack: gke-v2
|
||||
- name: HELM_ARGS
|
||||
value: ""
|
||||
name: cmp-helm-v2
|
||||
repoURL: https://github.com/mirakl/manifests-velero.git
|
||||
targetRevision: test-lla
|
||||
- deployStartedAt: "2024-03-04T22:50:55Z"
|
||||
deployedAt: "2024-03-04T22:50:55Z"
|
||||
id: 18
|
||||
revision: ea8759964626a583667a2bfd08f334ec2070040a
|
||||
source:
|
||||
path: instances/test
|
||||
plugin:
|
||||
env:
|
||||
- name: RELEASE_NAME
|
||||
value: test-lla
|
||||
- name: CHART_REPOSITORY
|
||||
value: oci://europe-west1-docker.pkg.dev/platform-89be/charts
|
||||
- name: CHART_NAME
|
||||
value: velero
|
||||
- name: PREVIEW
|
||||
value: "false"
|
||||
- name: HELM_VALUES
|
||||
value: |
|
||||
global:
|
||||
app:
|
||||
cluster_name: gke-alpha-01-europe-west1
|
||||
service_class: alpha
|
||||
cloud_provider: gcp
|
||||
cluster_stack: gke-v2
|
||||
- name: HELM_ARGS
|
||||
value: ""
|
||||
name: cmp-helm-v2
|
||||
repoURL: https://github.com/mirakl/manifests-velero.git
|
||||
targetRevision: test-lla
|
||||
- deployStartedAt: "2024-03-04T22:52:56Z"
|
||||
deployedAt: "2024-03-04T22:52:56Z"
|
||||
id: 19
|
||||
revision: ea8759964626a583667a2bfd08f334ec2070040a
|
||||
source:
|
||||
path: instances/test
|
||||
plugin:
|
||||
env:
|
||||
- name: RELEASE_NAME
|
||||
value: test-lla
|
||||
- name: CHART_REPOSITORY
|
||||
value: oci://europe-west1-docker.pkg.dev/platform-89be/charts
|
||||
- name: CHART_NAME
|
||||
value: velero
|
||||
- name: PREVIEW
|
||||
value: "false"
|
||||
- name: HELM_VALUES
|
||||
value: |
|
||||
global:
|
||||
app:
|
||||
cluster_name: gke-alpha-01-europe-west1
|
||||
service_class: alpha
|
||||
cloud_provider: gcp
|
||||
cluster_stack: gke-v2
|
||||
- name: HELM_ARGS
|
||||
value: ""
|
||||
name: cmp-helm-v2
|
||||
repoURL: https://github.com/mirakl/manifests-velero.git
|
||||
targetRevision: test-lla
|
||||
- deployStartedAt: "2024-03-04T22:56:15Z"
|
||||
deployedAt: "2024-03-04T22:56:15Z"
|
||||
id: 20
|
||||
revision: ea8759964626a583667a2bfd08f334ec2070040a
|
||||
source:
|
||||
path: instances/test
|
||||
plugin:
|
||||
env:
|
||||
- name: RELEASE_NAME
|
||||
value: test-lla
|
||||
- name: CHART_REPOSITORY
|
||||
value: oci://europe-west1-docker.pkg.dev/platform-89be/charts
|
||||
- name: CHART_NAME
|
||||
value: velero
|
||||
- name: PREVIEW
|
||||
value: "false"
|
||||
- name: HELM_VALUES
|
||||
value: |
|
||||
global:
|
||||
app:
|
||||
cluster_name: gke-alpha-01-europe-west1
|
||||
service_class: alpha
|
||||
cloud_provider: gcp
|
||||
cluster_stack: gke-v2
|
||||
- name: HELM_ARGS
|
||||
value: ""
|
||||
name: cmp-helm-v2
|
||||
repoURL: https://github.com/mirakl/manifests-velero.git
|
||||
targetRevision: test-lla
|
||||
- deployStartedAt: "2024-03-05T07:31:56Z"
|
||||
deployedAt: "2024-03-05T07:31:57Z"
|
||||
id: 21
|
||||
revision: ea8759964626a583667a2bfd08f334ec2070040a
|
||||
source:
|
||||
path: instances/test
|
||||
plugin:
|
||||
env:
|
||||
- name: RELEASE_NAME
|
||||
value: test-lla
|
||||
- name: CHART_REPOSITORY
|
||||
value: oci://europe-west1-docker.pkg.dev/platform-89be/charts
|
||||
- name: CHART_NAME
|
||||
value: velero
|
||||
- name: PREVIEW
|
||||
value: "false"
|
||||
- name: HELM_VALUES
|
||||
value: |
|
||||
global:
|
||||
app:
|
||||
cluster_name: gke-alpha-01-europe-west1
|
||||
service_class: alpha
|
||||
cloud_provider: gcp
|
||||
cluster_stack: gke-v2
|
||||
- name: HELM_ARGS
|
||||
value: ""
|
||||
name: cmp-helm-v2
|
||||
repoURL: https://github.com/mirakl/manifests-velero.git
|
||||
targetRevision: test-lla
|
||||
- deployStartedAt: "2024-03-05T07:32:44Z"
|
||||
deployedAt: "2024-03-05T07:32:44Z"
|
||||
id: 22
|
||||
revision: ea8759964626a583667a2bfd08f334ec2070040a
|
||||
source:
|
||||
path: instances/test
|
||||
plugin:
|
||||
env:
|
||||
- name: RELEASE_NAME
|
||||
value: test-lla
|
||||
- name: CHART_REPOSITORY
|
||||
value: oci://europe-west1-docker.pkg.dev/platform-89be/charts
|
||||
- name: CHART_NAME
|
||||
value: velero
|
||||
- name: PREVIEW
|
||||
value: "false"
|
||||
- name: HELM_VALUES
|
||||
value: |
|
||||
global:
|
||||
app:
|
||||
cluster_name: gke-alpha-01-europe-west1
|
||||
service_class: alpha
|
||||
cloud_provider: gcp
|
||||
cluster_stack: gke-v2
|
||||
- name: HELM_ARGS
|
||||
value: ""
|
||||
name: cmp-helm-v2
|
||||
repoURL: https://github.com/mirakl/manifests-velero.git
|
||||
targetRevision: test-lla
|
||||
- deployStartedAt: "2024-03-05T07:33:03Z"
|
||||
deployedAt: "2024-03-05T07:33:04Z"
|
||||
id: 23
|
||||
revision: ea8759964626a583667a2bfd08f334ec2070040a
|
||||
source:
|
||||
path: instances/test
|
||||
plugin:
|
||||
env:
|
||||
- name: RELEASE_NAME
|
||||
value: test-lla
|
||||
- name: CHART_REPOSITORY
|
||||
value: oci://europe-west1-docker.pkg.dev/platform-89be/charts
|
||||
- name: CHART_NAME
|
||||
value: velero
|
||||
- name: PREVIEW
|
||||
value: "false"
|
||||
- name: HELM_VALUES
|
||||
value: |
|
||||
global:
|
||||
app:
|
||||
cluster_name: gke-alpha-01-europe-west1
|
||||
service_class: alpha
|
||||
cloud_provider: gcp
|
||||
cluster_stack: gke-v2
|
||||
- name: HELM_ARGS
|
||||
value: ""
|
||||
name: cmp-helm-v2
|
||||
repoURL: https://github.com/mirakl/manifests-velero.git
|
||||
targetRevision: test-lla
|
||||
operationState:
|
||||
finishedAt: "2024-03-05T07:33:04Z"
|
||||
message: successfully synced (all tasks run)
|
||||
operation:
|
||||
initiatedBy:
|
||||
username: laurent.lavaud@mirakl.com
|
||||
retry:
|
||||
backoff:
|
||||
duration: 5s
|
||||
factor: 2
|
||||
maxDuration: 3m
|
||||
limit: 10
|
||||
sync:
|
||||
revision: ea8759964626a583667a2bfd08f334ec2070040a
|
||||
syncOptions:
|
||||
- ServerSideApply=true
|
||||
syncStrategy:
|
||||
hook: {}
|
||||
phase: Succeeded
|
||||
startedAt: "2024-03-05T07:33:03Z"
|
||||
syncResult:
|
||||
resources:
|
||||
- group: ""
|
||||
hookPhase: Running
|
||||
kind: Service
|
||||
message: service/test-lla serverside-applied
|
||||
name: test-lla
|
||||
namespace: test-lla
|
||||
status: Synced
|
||||
syncPhase: Sync
|
||||
version: v1
|
||||
- group: apps
|
||||
hookPhase: Running
|
||||
kind: Deployment
|
||||
message: deployment.apps/test-lla serverside-applied
|
||||
name: test-lla
|
||||
namespace: test-lla
|
||||
status: Synced
|
||||
syncPhase: Sync
|
||||
version: v1
|
||||
revision: ea8759964626a583667a2bfd08f334ec2070040a
|
||||
source:
|
||||
path: instances/test
|
||||
plugin:
|
||||
env:
|
||||
- name: RELEASE_NAME
|
||||
value: test-lla
|
||||
- name: CHART_REPOSITORY
|
||||
value: oci://europe-west1-docker.pkg.dev/platform-89be/charts
|
||||
- name: CHART_NAME
|
||||
value: velero
|
||||
- name: PREVIEW
|
||||
value: "false"
|
||||
- name: HELM_VALUES
|
||||
value: |
|
||||
global:
|
||||
app:
|
||||
cluster_name: gke-alpha-01-europe-west1
|
||||
service_class: alpha
|
||||
cloud_provider: gcp
|
||||
cluster_stack: gke-v2
|
||||
- name: HELM_ARGS
|
||||
value: ""
|
||||
name: cmp-helm-v2
|
||||
repoURL: https://github.com/mirakl/manifests-velero.git
|
||||
targetRevision: test-lla
|
||||
reconciledAt: "2024-03-05T07:33:04Z"
|
||||
resources:
|
||||
- health:
|
||||
status: Healthy
|
||||
kind: Service
|
||||
name: test-lla
|
||||
namespace: test-lla
|
||||
status: Synced
|
||||
version: v1
|
||||
- group: apps
|
||||
health:
|
||||
status: Healthy
|
||||
kind: Deployment
|
||||
name: test-lla
|
||||
namespace: test-lla
|
||||
status: Synced
|
||||
version: v1
|
||||
sourceType: Plugin
|
||||
summary:
|
||||
images:
|
||||
- nginx:latest
|
||||
sync:
|
||||
comparedTo:
|
||||
destination:
|
||||
name: gke-alpha-01-europe-west1
|
||||
namespace: test-lla
|
||||
source:
|
||||
path: instances/test
|
||||
plugin:
|
||||
env:
|
||||
- name: RELEASE_NAME
|
||||
value: test-lla
|
||||
- name: CHART_REPOSITORY
|
||||
value: oci://europe-west1-docker.pkg.dev/platform-89be/charts
|
||||
- name: CHART_NAME
|
||||
value: velero
|
||||
- name: PREVIEW
|
||||
value: "false"
|
||||
- name: HELM_VALUES
|
||||
value: |
|
||||
global:
|
||||
app:
|
||||
cluster_name: gke-alpha-01-europe-west1
|
||||
service_class: alpha
|
||||
cloud_provider: gcp
|
||||
cluster_stack: gke-v2
|
||||
- name: HELM_ARGS
|
||||
value: ""
|
||||
name: cmp-helm-v2
|
||||
repoURL: https://github.com/mirakl/manifests-velero.git
|
||||
targetRevision: test-lla
|
||||
revision: rev1
|
||||
status: Synced
|
||||
177
controller/testdata/live-deployment-env-vars.yaml
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
argocd.argoproj.io/tracking-id: 'guestbook:apps/Deployment:default/kustomize-guestbook-ui'
|
||||
deployment.kubernetes.io/revision: '9'
|
||||
iksm-version: '2.0'
|
||||
kubectl.kubernetes.io/last-applied-configuration: >
|
||||
{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"argocd.argoproj.io/tracking-id":"guestbook:apps/Deployment:default/kustomize-guestbook-ui","iksm-version":"2.0"},"name":"kustomize-guestbook-ui","namespace":"default"},"spec":{"replicas":4,"revisionHistoryLimit":3,"selector":{"matchLabels":{"app":"guestbook-ui"}},"template":{"metadata":{"labels":{"app":"guestbook-ui"}},"spec":{"containers":[{"env":[{"name":"SOME_ENV_VAR","value":"some_value"}],"image":"gcr.io/heptio-images/ks-guestbook-demo:0.1","name":"guestbook-ui","ports":[{"containerPort":80}],"resources":{"requests":{"cpu":"50m","memory":"100Mi"}}}]}}}}
|
||||
creationTimestamp: '2022-01-05T15:45:21Z'
|
||||
generation: 119
|
||||
managedFields:
|
||||
- apiVersion: apps/v1
|
||||
fieldsType: FieldsV1
|
||||
fieldsV1:
|
||||
'f:metadata':
|
||||
'f:annotations':
|
||||
'f:iksm-version': {}
|
||||
manager: janitor
|
||||
operation: Apply
|
||||
time: '2022-01-06T18:21:04Z'
|
||||
- apiVersion: apps/v1
|
||||
fieldsType: FieldsV1
|
||||
fieldsV1:
|
||||
'f:metadata':
|
||||
'f:annotations':
|
||||
.: {}
|
||||
'f:argocd.argoproj.io/tracking-id': {}
|
||||
'f:kubectl.kubernetes.io/last-applied-configuration': {}
|
||||
'f:spec':
|
||||
'f:progressDeadlineSeconds': {}
|
||||
'f:replicas': {}
|
||||
'f:revisionHistoryLimit': {}
|
||||
'f:selector': {}
|
||||
'f:strategy':
|
||||
'f:rollingUpdate':
|
||||
.: {}
|
||||
'f:maxSurge': {}
|
||||
'f:maxUnavailable': {}
|
||||
'f:type': {}
|
||||
'f:template':
|
||||
'f:metadata':
|
||||
'f:labels':
|
||||
.: {}
|
||||
'f:app': {}
|
||||
'f:spec':
|
||||
'f:containers':
|
||||
'k:{"name":"guestbook-ui"}':
|
||||
.: {}
|
||||
'f:env':
|
||||
.: {}
|
||||
'k:{"name":"SOME_ENV_VAR"}':
|
||||
.: {}
|
||||
'f:name': {}
|
||||
'f:value': {}
|
||||
'f:image': {}
|
||||
'f:imagePullPolicy': {}
|
||||
'f:name': {}
|
||||
'f:ports':
|
||||
.: {}
|
||||
'k:{"containerPort":80,"protocol":"TCP"}':
|
||||
.: {}
|
||||
'f:containerPort': {}
|
||||
'f:protocol': {}
|
||||
'f:resources':
|
||||
.: {}
|
||||
'f:requests':
|
||||
.: {}
|
||||
'f:cpu': {}
|
||||
'f:memory': {}
|
||||
'f:terminationMessagePath': {}
|
||||
'f:terminationMessagePolicy': {}
|
||||
'f:dnsPolicy': {}
|
||||
'f:restartPolicy': {}
|
||||
'f:schedulerName': {}
|
||||
'f:securityContext': {}
|
||||
'f:terminationGracePeriodSeconds': {}
|
||||
manager: argocd
|
||||
operation: Update
|
||||
time: '2022-01-06T15:04:15Z'
|
||||
- apiVersion: apps/v1
|
||||
fieldsType: FieldsV1
|
||||
fieldsV1:
|
||||
'f:metadata':
|
||||
'f:annotations':
|
||||
'f:deployment.kubernetes.io/revision': {}
|
||||
'f:status':
|
||||
'f:availableReplicas': {}
|
||||
'f:conditions':
|
||||
.: {}
|
||||
'k:{"type":"Available"}':
|
||||
.: {}
|
||||
'f:lastTransitionTime': {}
|
||||
'f:lastUpdateTime': {}
|
||||
'f:message': {}
|
||||
'f:reason': {}
|
||||
'f:status': {}
|
||||
'f:type': {}
|
||||
'k:{"type":"Progressing"}':
|
||||
.: {}
|
||||
'f:lastTransitionTime': {}
|
||||
'f:lastUpdateTime': {}
|
||||
'f:message': {}
|
||||
'f:reason': {}
|
||||
'f:status': {}
|
||||
'f:type': {}
|
||||
'f:observedGeneration': {}
|
||||
'f:readyReplicas': {}
|
||||
'f:replicas': {}
|
||||
'f:updatedReplicas': {}
|
||||
manager: kube-controller-manager
|
||||
operation: Update
|
||||
time: '2022-01-06T18:15:14Z'
|
||||
name: kustomize-guestbook-ui
|
||||
namespace: default
|
||||
resourceVersion: '8289211'
|
||||
uid: ef253575-ce44-4c5e-84ad-16e81d0df6eb
|
||||
spec:
|
||||
progressDeadlineSeconds: 600
|
||||
replicas: 4
|
||||
revisionHistoryLimit: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: guestbook-ui
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 25%
|
||||
maxUnavailable: 25%
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: SOME_ENV_VAR
|
||||
value: some_value
|
||||
image: 'gcr.io/heptio-images/ks-guestbook-demo:0.1'
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
- containerPort: 80
|
||||
protocol: TCP
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 100Mi
|
||||
terminationMessagePath: /dev/termination-log
|
||||
terminationMessagePolicy: File
|
||||
dnsPolicy: ClusterFirst
|
||||
restartPolicy: Always
|
||||
schedulerName: default-scheduler
|
||||
securityContext: {}
|
||||
terminationGracePeriodSeconds: 30
|
||||
status:
|
||||
availableReplicas: 4
|
||||
conditions:
|
||||
- lastTransitionTime: '2022-01-05T22:20:37Z'
|
||||
lastUpdateTime: '2022-01-05T22:43:47Z'
|
||||
message: >-
|
||||
ReplicaSet "kustomize-guestbook-ui-6549d54677" has successfully
|
||||
progressed.
|
||||
reason: NewReplicaSetAvailable
|
||||
status: 'True'
|
||||
type: Progressing
|
||||
- lastTransitionTime: '2022-01-06T18:15:14Z'
|
||||
lastUpdateTime: '2022-01-06T18:15:14Z'
|
||||
message: Deployment has minimum availability.
|
||||
reason: MinimumReplicasAvailable
|
||||
status: 'True'
|
||||
type: Available
|
||||
observedGeneration: 119
|
||||
readyReplicas: 4
|
||||
replicas: 4
|
||||
updatedReplicas: 4
|
||||
14
controller/testdata/live-httpproxy.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: projectcontour.io/v1
|
||||
kind: HTTPProxy
|
||||
metadata:
|
||||
name: my-http-proxy
|
||||
namespace: default
|
||||
spec:
|
||||
routes:
|
||||
- rateLimitPolicy:
|
||||
global:
|
||||
descriptors:
|
||||
- entries:
|
||||
- requestHeader:
|
||||
descriptorKey: sample-key
|
||||
headerName: sample-header
|
||||
21
controller/testdata/minimal-image-replicas-deployment.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: client
|
||||
name: client
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: client
|
||||
strategy: {}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: client
|
||||
spec:
|
||||
containers:
|
||||
- image: alpine:3
|
||||
name: alpine
|
||||
resources: {}
|
||||
35
controller/testdata/target-deployment-env-vars.yaml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
argocd.argoproj.io/tracking-id: 'guestbook:apps/Deployment:default/kustomize-guestbook-ui'
|
||||
iksm-version: '1.0'
|
||||
name: kustomize-guestbook-ui
|
||||
namespace: default
|
||||
spec:
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: guestbook-ui
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: SOME_OTHER_ENV_VAR
|
||||
value: some_other_value
|
||||
- name: YET_ANOTHER_ENV_VAR
|
||||
value: yet_another_value
|
||||
- name: SOME_ENV_VAR
|
||||
value: different_value!
|
||||
image: 'gcr.io/heptio-images/ks-guestbook-demo:0.1'
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
- containerPort: 80
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 100Mi
|
||||
23
controller/testdata/target-httpproxy.yaml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
apiVersion: projectcontour.io/v1
|
||||
kind: HTTPProxy
|
||||
metadata:
|
||||
name: my-http-proxy
|
||||
namespace: default
|
||||
spec:
|
||||
routes:
|
||||
- rateLimitPolicy:
|
||||
global:
|
||||
descriptors:
|
||||
- entries:
|
||||
- requestHeaderValueMatch:
|
||||
headers:
|
||||
- contains: sample-key
|
||||
name: sample-header
|
||||
value: third
|
||||
- requestHeader:
|
||||
descriptorKey: sample-key
|
||||
headerName: sample-header
|
||||
- entries:
|
||||
- requestHeader:
|
||||
descriptorKey: sample-key
|
||||
headerName: sample-header
|
||||
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 58 KiB |
BIN
docs/assets/okta-app.png
Normal file
|
After Width: | Height: | Size: 254 KiB |
BIN
docs/assets/okta-auth-policy.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
docs/assets/okta-auth-rule.png
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
docs/assets/okta-create-oidc-app.png
Normal file
|
After Width: | Height: | Size: 352 KiB |
BIN
docs/assets/okta-groups-claim.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
docs/assets/okta-groups-scope.png
Normal file
|
After Width: | Height: | Size: 183 KiB |
@@ -37,6 +37,17 @@ sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
|
||||
rm argocd-linux-amd64
|
||||
```
|
||||
|
||||
#### Download latest stable version
|
||||
|
||||
You can download the latest stable release by executing below steps:
|
||||
|
||||
```bash
|
||||
VERSION=$(curl -L -s https://raw.githubusercontent.com/argoproj/argo-cd/stable/VERSION)
|
||||
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/download/v$VERSION/argocd-linux-amd64
|
||||
sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
|
||||
rm argocd-linux-amd64
|
||||
```
|
||||
|
||||
You should now be able to run `argocd` commands.
|
||||
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ and the CLI functionalities.
|
||||
### Application Controller
|
||||
|
||||
The Application Controller is responsible for reconciling the
|
||||
Application resource in Kubernetes syncronizing the desired
|
||||
Application resource in Kubernetes synchronizing the desired
|
||||
application state (provided in Git) with the live state (in
|
||||
Kubernetes). The Application Controller is also responsible for
|
||||
reconciling the Project resource.
|
||||
|
||||
@@ -103,10 +103,12 @@ 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 8:15AM Pacific Time ([click here to check in your current timezone][1]). We use Zoom to conduct these meetings.
|
||||
|
||||
* [Agenda document (Google Docs, includes Zoom link)](https://docs.google.com/document/d/1xkoFkVviB70YBzSEa4bDnu-rUZ1sIFtwKKG1Uw8XsY8)
|
||||
|
||||
If you want to discuss something, we kindly ask you to put your item on the
|
||||
[agenda](https://docs.google.com/document/d/1xkoFkVviB70YBzSEa4bDnu-rUZ1sIFtwKKG1Uw8XsY8)
|
||||
for one of the upcoming meetings so that we can plan in the time for discussing it.
|
||||
for one of the upcoming meetings so that we can plan in the time for discussing it.
|
||||
|
||||
[1]: https://www.timebie.com/std/pacific.php?q=081500
|
||||
|
||||
@@ -9,7 +9,9 @@ and the [toolchain guide](toolchain-guide.md).
|
||||
|
||||
### Install Go
|
||||
|
||||
Install version 1.18 or newer (Verify version by running `go version`)
|
||||
<https://go.dev/doc/install/>
|
||||
|
||||
Install Go with a version equal to or greater than the version listed in `go.mod` (verify go version with `go version`).
|
||||
|
||||
### Clone the Argo CD repo
|
||||
|
||||
@@ -23,16 +25,29 @@ git clone https://github.com/argoproj/argo-cd.git
|
||||
|
||||
<https://docs.docker.com/engine/install/>
|
||||
|
||||
### Install or Upgrade `kind` (Optional - Should work with any local cluster)
|
||||
### Install or Upgrade a Tool for Running Local Clusters (e.g. kind or minikube)
|
||||
|
||||
#### Installation guide for kind:
|
||||
|
||||
<https://kind.sigs.k8s.io/docs/user/quick-start/>
|
||||
|
||||
#### Installation guide for minikube:
|
||||
|
||||
<https://minikube.sigs.k8s.io/docs/start/>
|
||||
|
||||
### Start Your Local Cluster
|
||||
|
||||
For example, if you are using kind:
|
||||
```shell
|
||||
kind create cluster
|
||||
```
|
||||
|
||||
Or, if you are using minikube:
|
||||
|
||||
```shell
|
||||
minikube start
|
||||
```
|
||||
|
||||
### Install Argo CD
|
||||
|
||||
```shell
|
||||
|
||||
@@ -15,7 +15,7 @@ requests before forwarding to the backend service.
|
||||
|
||||
As proxy extension is in [Alpha][1] phase, the feature is disabled by
|
||||
default. To enable it, it is necessary to configure the feature flag
|
||||
in Argo CD command parameters. The easiest way to to properly enable
|
||||
in Argo CD command parameters. The easiest way to properly enable
|
||||
this feature flag is by adding the `server.enable.proxy.extension` key
|
||||
in the existing `argocd-cmd-params-cm`. For example:
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ These are the upcoming releases dates:
|
||||
| v2.8 | Monday, Jun. 26, 2023 | Monday, Aug. 7, 2023 | [Keith Chong](https://github.com/keithchong) | [Keith Chong](https://github.com/keithchong) | [checklist](https://github.com/argoproj/argo-cd/issues/13742) |
|
||||
| v2.9 | Monday, Sep. 18, 2023 | Monday, Nov. 6, 2023 | [Leonardo Almeida](https://github.com/leoluz) | [Leonardo Almeida](https://github.com/leoluz) | [checklist](https://github.com/argoproj/argo-cd/issues/14078) |
|
||||
| v2.10 | Monday, Dec. 18, 2023 | Monday, Feb. 5, 2024 | [Katie Lamkin](https://github.com/kmlamkin9) | | [checklist](https://github.com/argoproj/argo-cd/issues/16339) |
|
||||
| v2.11 | Monday, Mar. 18, 2024 | Monday, May 6, 2024 |
|
||||
| v2.11 | Friday, Apr. 5, 2024 | Monday, May 6, 2024 | [Pavel Kostohrys](https://github.com/pasha-codefresh) | [Pavel Kostohrys](https://github.com/pasha-codefresh) | [checklist](https://github.com/argoproj/argo-cd/issues/17726) |
|
||||
| v2.12 | Monday, Jun. 17, 2024 | Monday, Aug. 5, 2024 |
|
||||
|
||||
Actual release dates might differ from the plan by a few days.
|
||||
@@ -71,7 +71,7 @@ that minor release. It will have to wait for the next minor release.
|
||||
|
||||
### Security Patch Policy
|
||||
|
||||
CVEs in Argo CD code will be patched for all [supported versions](../operator-manual/installation.md#supported-versions).
|
||||
CVEs in Argo CD code will be patched for all supported versions. Read more about supported versions in the [security policy for Argo CD](https://github.com/argoproj/argo-cd/security/policy#supported-versions).
|
||||
|
||||
### Dependencies Lifecycle Policy
|
||||
|
||||
|
||||
@@ -2,20 +2,19 @@
|
||||
|
||||
## Developing And Testing
|
||||
|
||||
The website is built using `mkdocs` and `mkdocs-material`.
|
||||
The website is built using `mkdocs` and `mkdocs-material`.
|
||||
|
||||
To test:
|
||||
|
||||
```bash
|
||||
make serve-docs
|
||||
```
|
||||
Once running, you can view your locally built documentation at [http://0.0.0.0:8000/](http://0.0.0.0:8000/).
|
||||
Make a change to documentation and the website will rebuild and refresh the view.
|
||||
|
||||
Once running, you can view your locally built documentation at [http://0.0.0.0:8000/](http://0.0.0.0:8000/).
|
||||
|
||||
## Deploying
|
||||
|
||||
Before submitting a PR build the website, to verify that there are no errors building the site
|
||||
```bash
|
||||
make publish-docs
|
||||
make build-docs
|
||||
```
|
||||
|
||||
## Analytics
|
||||
@@ -23,4 +22,4 @@ make publish-docs
|
||||
!!! tip
|
||||
Don't forget to disable your ad-blocker when testing.
|
||||
|
||||
We collect [Google Analytics](https://analytics.google.com/analytics/web/#/report-home/a105170809w198079555p192782995).
|
||||
We collect [Google Analytics](https://analytics.google.com/analytics/web/#/report-home/a105170809w198079555p192782995).
|
||||
|
||||
@@ -138,6 +138,14 @@ The following steps are required no matter whether you chose to use a virtualize
|
||||
export SUDO=sudo
|
||||
```
|
||||
|
||||
If you have podman installed, you can also leverage its rootless mode. In
|
||||
order to use podman for running and testing Argo CD locally, set the
|
||||
`DOCKER` environment variable to `podman` before you run `make`, e.g.
|
||||
|
||||
```
|
||||
DOCKER=podman make start
|
||||
```
|
||||
|
||||
### Clone the Argo CD repository from your personal fork on GitHub
|
||||
|
||||
* `mkdir -p ~/go/src/github.com/argoproj`
|
||||
@@ -304,7 +312,7 @@ For installing the tools required to build and test Argo CD on your local system
|
||||
You can change the target location by setting the `BIN` environment before running the installer scripts. For example, you can install the binaries into `~/go/bin` (which should then be the first component in your `PATH` environment, i.e. `export PATH=~/go/bin:$PATH`):
|
||||
|
||||
```shell
|
||||
make BIN=~/go/bin install-tools-local
|
||||
BIN=~/go/bin make install-tools-local
|
||||
```
|
||||
|
||||
Additionally, you have to install at least the following tools via your OS's package manager (this list might not be always up-to-date):
|
||||
|
||||
56
docs/faq.md
@@ -36,7 +36,7 @@ which might cause health check to return `Progressing` state instead of `Healthy
|
||||
As workaround Argo CD allows providing [health check](operator-manual/health.md) customization which overrides default
|
||||
behavior.
|
||||
|
||||
If you are using Traefik for your Ingress, you can update the Traefik config to publish the loadBalancer IP using [publishedservice](https://doc.traefik.io/traefik/providers/kubernetes-ingress/#publishedservice), which will resolve this issue.
|
||||
If you are using Traefik for your Ingress, you can update the Traefik config to publish the loadBalancer IP using [publishedservice](https://doc.traefik.io/traefik/providers/kubernetes-ingress/#publishedservice), which will resolve this issue.
|
||||
|
||||
```yaml
|
||||
providers:
|
||||
@@ -97,7 +97,7 @@ data:
|
||||
|
||||
## After deploying my Helm application with Argo CD I cannot see it with `helm ls` and other Helm commands
|
||||
|
||||
When deploying a Helm application Argo CD is using Helm
|
||||
When deploying a Helm application Argo CD is using Helm
|
||||
only as a template mechanism. It runs `helm template` and
|
||||
then deploys the resulting manifests on the cluster instead of doing `helm install`. This means that you cannot use any Helm command
|
||||
to view/verify the application. It is fully managed by Argo CD.
|
||||
@@ -140,15 +140,15 @@ Argo CD automatically sets the `app.kubernetes.io/instance` label and uses it to
|
||||
If the tool does this too, this causes confusion. You can change this label by setting
|
||||
the `application.instanceLabelKey` value in the `argocd-cm`. We recommend that you use `argocd.argoproj.io/instance`.
|
||||
|
||||
!!! note
|
||||
!!! note
|
||||
When you make this change your applications will become out of sync and will need re-syncing.
|
||||
|
||||
See [#1482](https://github.com/argoproj/argo-cd/issues/1482).
|
||||
|
||||
## How often does Argo CD check for changes to my Git or Helm repository ?
|
||||
|
||||
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, Argo CD 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.
|
||||
The default polling interval is 3 minutes (180 seconds) with a configurable jitter.
|
||||
You can change the setting by updating the `timeout.reconciliation` value and the `timeout.reconciliation.jitter` 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, Argo CD 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`?
|
||||
@@ -250,7 +250,7 @@ There are two parts to the message:
|
||||
|
||||
> 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
|
||||
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
|
||||
|
||||
`]`
|
||||
|
||||
@@ -259,7 +259,7 @@ There are two parts to the message:
|
||||
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.
|
||||
@@ -268,3 +268,45 @@ 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.
|
||||
|
||||
## How to rotate Redis secret?
|
||||
* Delete `argocd-redis` secret in the namespace where Argo CD is installed.
|
||||
```bash
|
||||
kubectl delete secret argocd-redis -n <argocd namesapce>
|
||||
```
|
||||
* If you are running Redis in HA mode, restart Redis in HA.
|
||||
```bash
|
||||
kubectl rollout restart deployment argocd-redis-ha-haproxy
|
||||
kubectl rollout restart statefulset argocd-redis-ha-server
|
||||
```
|
||||
* If you are running Redis in non-HA mode, restart Redis.
|
||||
```bash
|
||||
kubectl rollout restart deployment argocd-redis
|
||||
```
|
||||
* Restart other components.
|
||||
```bash
|
||||
kubectl rollout restart deployment argocd-server argocd-repo-server
|
||||
kubectl rollout restart statefulset argocd-application-controller
|
||||
```
|
||||
|
||||
## How to turn off Redis auth if users really want to?
|
||||
|
||||
Argo CD default installation is now configured automatically enable Redis authentication.
|
||||
If for some reason authenticated Redis does not work for you and you want to use non-authenticated Redis, here are the steps:
|
||||
|
||||
* You need to have your own Redis installation.
|
||||
* Configure Argo CD to use your own Redis instance. See this [doc](https://argo-cd.readthedocs.io/en/stable/operator-manual/argocd-cmd-params-cm-yaml/) for the Argo CD configuration.
|
||||
* If you already installed Redis shipped with Argo CD, you also need to clean up the existing components:
|
||||
* When HA Redis is used:
|
||||
* kubectl delete deployment argocd-redis-ha-haproxy
|
||||
* kubectl delete statefulset argocd-redis-ha-server
|
||||
* When non-HA Redis is used:
|
||||
* kubectl delete deployment argocd-redis
|
||||
* Remove environment variable `REDIS_PASSWORD` from the following manifests
|
||||
* Deployment: argocd-repo-server:
|
||||
* Deployment: argocd-server
|
||||
* StatefulSet: argocd-application-controller
|
||||
|
||||
## How do I provide my own Redis credentials?
|
||||
The Redis password is stored in Kubernetes secret `argocd-redis` with key `auth` in the namespace where Argo CD is installed.
|
||||
You can config your secret provider to generate Kubernetes secret accordingly.
|
||||
@@ -22,12 +22,8 @@ This will create a new namespace, `argocd`, where Argo CD services and applicati
|
||||
The installation manifests include `ClusterRoleBinding` resources that reference `argocd` namespace. If you are installing Argo CD into a different
|
||||
namespace then make sure to update the namespace reference.
|
||||
|
||||
If you are not interested in UI, SSO, multi-cluster features then you can install [core](operator-manual/installation.md#core) Argo CD components only:
|
||||
|
||||
```bash
|
||||
kubectl create namespace argocd
|
||||
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/core-install.yaml
|
||||
```
|
||||
!!! tip
|
||||
If you are not interested in UI, SSO, and multi-cluster features, then you can install only the [core](operator-manual/core/#installing) Argo CD components.
|
||||
|
||||
This default installation will have a self-signed certificate and cannot be accessed without a bit of extra work.
|
||||
Do one of:
|
||||
@@ -36,8 +32,17 @@ Do one of:
|
||||
* Configure the client OS to trust the self signed certificate.
|
||||
* Use the --insecure flag on all Argo CD CLI operations in this guide.
|
||||
|
||||
!!! note
|
||||
Default namespace for `kubectl` config must be set to `argocd`.
|
||||
This is only needed for the following commands since the previous commands have -n argocd already:
|
||||
`kubectl config set-context --current --namespace=argocd`
|
||||
|
||||
|
||||
Use `argocd login --core` to [configure](./user-guide/commands/argocd_login.md) CLI access and skip steps 3-5.
|
||||
|
||||
!!! note
|
||||
This default installation for Redis is using password authentication. The Redis password is stored in Kubernetes secret `argocd-redis` with key `auth` in the namespace where Argo CD is installed.
|
||||
|
||||
## 2. Download Argo CD CLI
|
||||
|
||||
Download the latest Argo CD version from [https://github.com/argoproj/argo-cd/releases/latest](https://github.com/argoproj/argo-cd/releases/latest). More detailed installation instructions can be found via the [CLI installation documentation](cli_installation.md).
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# 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.
|
||||
|
||||
@@ -13,10 +11,6 @@ Argo CD administrators can define a certain set of namespaces where `Application
|
||||
|
||||
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.
|
||||
|
||||
|
||||
One additional advantage of adopting applications in any namespace is to allow end-users to configure notifications for their Argo CD application in the namespace where Argo CD application is running in. See notifications [namespace based configuration](notifications/index.md#namespace-based-configuration) page for more information.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -119,7 +119,7 @@ spec:
|
||||
extVars:
|
||||
- name: foo
|
||||
value: bar
|
||||
# You can use "code to determine if the value is either string (false, the default) or Jsonnet code (if code is true).
|
||||
# You can use "code" to determine if the value is either string (false, the default) or Jsonnet code (if code is true).
|
||||
- code: true
|
||||
name: baz
|
||||
value: "true"
|
||||
|
||||
@@ -3,32 +3,217 @@ kind: ApplicationSet
|
||||
metadata:
|
||||
name: test-hello-world-appset
|
||||
namespace: argocd
|
||||
# To preserve this annotation and label we can use the preservedFields property
|
||||
preservedFields:
|
||||
# This annotation and label exists only on this Application, and not in
|
||||
# the parent ApplicationSet template:
|
||||
# ignoreApplicationDifferences is the preferred way to accomplish this now.
|
||||
annotations:
|
||||
my-custom-annotation: some-value
|
||||
labels:
|
||||
my-custom-label: some-value
|
||||
|
||||
spec:
|
||||
# See docs for available generators and their specs.
|
||||
generators:
|
||||
- list:
|
||||
elements:
|
||||
- cluster: https://kubernetes.default.svc
|
||||
|
||||
# Using a generator plugin without combining it with Matrix or Merge
|
||||
# Plugins allow you to provide your own generator
|
||||
- plugin:
|
||||
# Specify the configMap where the plugin configuration is located.
|
||||
configMapRef:
|
||||
name: my-plugin
|
||||
# You can pass arbitrary parameters to the plugin. `input.parameters` is a map, but values may be any type.
|
||||
# These parameters will also be available on the generator's output under the `generator.input.parameters` key.
|
||||
input:
|
||||
parameters:
|
||||
key1: "value1"
|
||||
key2: "value2"
|
||||
list: ["list", "of", "values"]
|
||||
boolean: true
|
||||
map:
|
||||
key1: "value1"
|
||||
key2: "value2"
|
||||
key3: "value3"
|
||||
# You can also attach arbitrary values to the generator's output under the `values` key. These values will be
|
||||
# available in templates under the `values` key.
|
||||
values:
|
||||
value1: something
|
||||
# When using a Plugin generator, the ApplicationSet controller polls every `requeueAfterSeconds` interval (defaulting to every 30 minutes) to detect changes.
|
||||
requeueAfterSeconds: 30
|
||||
|
||||
# to automatically discover repositories within an organization
|
||||
- scmProvider:
|
||||
# Which protocol to clone using.
|
||||
cloneProtocol: ssh
|
||||
# The GitHub mode uses the GitHub API to scan an organization in either github.com or GitHub Enterprise
|
||||
github:
|
||||
# The GitHub organization to scan.
|
||||
organization: myorg
|
||||
# For GitHub Enterprise:
|
||||
api: https://git.example.com/
|
||||
# If true, scan every branch of every repository. If false, scan only the default branch. Defaults to false.
|
||||
allBranches: true
|
||||
# Reference to a Secret containing an access token. (optional)
|
||||
tokenRef:
|
||||
secretName: github-token
|
||||
key: token
|
||||
# (optional) use a GitHub App to access the API instead of a PAT.
|
||||
appSecretName: gh-app-repo-creds
|
||||
#Pass additional key-value pairs via values field
|
||||
values:
|
||||
name: "{{organization}}-{{repository}}"
|
||||
|
||||
#The GitLab mode uses the GitLab API to scan and organization in either gitlab.com or self-hosted GitLab.
|
||||
gitlab:
|
||||
#The Gitea mode uses the Gitea API to scan organizations in your instance
|
||||
gitea:
|
||||
#Use the Bitbucket Server API (1.0) to scan repos in a project.
|
||||
bitbucketServer:
|
||||
#Uses the Azure DevOps API to look up eligible repositories
|
||||
azureDevOps:
|
||||
# The Bitbucket mode uses the Bitbucket API V2 to scan a workspace in bitbucket.org
|
||||
bitbucket:
|
||||
#Uses AWS ResourceGroupsTagging and AWS CodeCommit APIs to scan repos across AWS accounts and regionsz
|
||||
awsCodeCommit:
|
||||
|
||||
#Filters allow selecting which repositories to generate for.
|
||||
filters:
|
||||
# Include any repository starting with "myapp" AND including a Kustomize config AND labeled with "deploy-ok" ...
|
||||
- repositoryMatch: ^myapp
|
||||
pathsExist: [kubernetes/kustomization.yaml]
|
||||
labelMatch: deploy-ok
|
||||
# ... OR include any repository starting with "otherapp" AND a Helm folder and doesn't have file disabledrepo.txt.
|
||||
- repositoryMatch: ^otherapp
|
||||
pathsExist: [helm]
|
||||
pathsDoNotExist: [disabledrepo.txt]
|
||||
# matrix 'parent' generator
|
||||
- matrix:
|
||||
generators:
|
||||
# any of the top-level generators may be used here instead.
|
||||
|
||||
# merge 'parent' generator
|
||||
# Use the selector set by both child generators to combine them.
|
||||
- merge:
|
||||
mergeKeys:
|
||||
- server
|
||||
# Note that this would not work with goTemplate enabled,
|
||||
# nested merge keys are not supported there.
|
||||
- values.selector
|
||||
generators:
|
||||
- clusters:
|
||||
values:
|
||||
kafka: 'true'
|
||||
redis: 'false'
|
||||
# For clusters with a specific label, enable Kafka.
|
||||
- clusters:
|
||||
selector:
|
||||
matchLabels:
|
||||
use-kafka: 'false'
|
||||
values:
|
||||
kafka: 'false'
|
||||
# For a specific cluster, enable Redis.
|
||||
- list:
|
||||
elements:
|
||||
- server: https://2.4.6.8
|
||||
values.redis: 'true'
|
||||
|
||||
|
||||
# Determines whether go templating will be used in the `template` field below.
|
||||
goTemplate: false
|
||||
goTemplate: true
|
||||
# Optional list of go templating options, see https://pkg.go.dev/text/template#Template.Option
|
||||
# This is only relevant if `goTemplate` is true
|
||||
goTemplateOptions: ["missingkey="]
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
|
||||
# These fields are identical to the Application spec.
|
||||
# The generator's template field takes precedence over the spec's template fields
|
||||
template:
|
||||
metadata:
|
||||
name: test-hello-world-app
|
||||
spec:
|
||||
project: my-project
|
||||
syncPolicy:
|
||||
automated:
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
# defines from which Git repository to extract the desired Application manifests
|
||||
source:
|
||||
- chart: '{{.chart}}'
|
||||
# developers may customize app details using JSON files from above repo URL
|
||||
repoURL: https://github.com/argoproj/argo-cd.git
|
||||
targetRevision: HEAD
|
||||
# Path within the repository where Kubernetes manifests are located
|
||||
path: applicationset/examples/list-generator/guestbook/{{cluster}}
|
||||
helm:
|
||||
useCredentials: "{{.useCredentials}}" # This field may NOT be templated, because it is a boolean field
|
||||
parameters:
|
||||
- name: "image.tag"
|
||||
value: "pull-{{head_sha}}"
|
||||
- name: "{{.name}}"
|
||||
value: "{{.value}}"
|
||||
- name: throw-away
|
||||
value: "{{end}}"
|
||||
destination:
|
||||
# Only one of name or server may be specified: if both are specified, an error is returned.
|
||||
# Name of the cluster (within Argo CD) to deploy to
|
||||
name: production-cluster # cluster is restricted
|
||||
# API Server URL for the cluster
|
||||
server: '{{.url}}'
|
||||
# Target namespace in which to deploy the manifests from source
|
||||
namespace: dev-team-one # namespace is restricted
|
||||
|
||||
# This sync policy pertains to the ApplicationSet, not to the Applications it creates.
|
||||
syncPolicy:
|
||||
# Determines whether the controller will delete Applications when an ApplicationSet is deleted.
|
||||
preserveResourcesOnDeletion: false
|
||||
# Alpha feature to determine the order in which ApplicationSet applies changes.
|
||||
# Prevents ApplicationSet controller from modifying or deleting Applications
|
||||
applicationsSync: create-only
|
||||
|
||||
# Prevents ApplicationSet controller from deleting Applications. Update is allowed
|
||||
# applicationsSync: create-update
|
||||
|
||||
# Prevents ApplicationSet controller from modifying Applications. Delete is allowed.
|
||||
# applicationsSync: create-delete
|
||||
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
# Prevent an Application's child resources from being deleted, when the parent Application is deleted
|
||||
preserveResourcesOnDeletion: true
|
||||
|
||||
# which fields of the ApplicationSet should be ignored when comparing Applications.
|
||||
ignoreApplicationDifferences:
|
||||
- jsonPointers:
|
||||
- /spec/source/targetRevision
|
||||
- name: some-app
|
||||
jqExpressions:
|
||||
- .spec.source.helm.values
|
||||
|
||||
strategy:
|
||||
# This field lets you define fields which should be ignored when applying Application resources. This is helpful if you
|
||||
# want to use ApplicationSets to create apps, but also want to allow users to modify those apps without having their
|
||||
# changes overwritten by the ApplicationSet.
|
||||
# This update strategy allows you to group Applications by labels present on the generated Application resources
|
||||
type: RollingSync
|
||||
rollingSync:
|
||||
steps:
|
||||
# Application groups are selected using their labels and matchExpressions
|
||||
- matchExpressions:
|
||||
- key: envLabel
|
||||
operator: In
|
||||
values:
|
||||
- env-dev
|
||||
# maxUpdate: 100% # if undefined, all applications matched are updated together (default is 100%)
|
||||
- matchExpressions:
|
||||
- key: envLabel
|
||||
operator: In
|
||||
values:
|
||||
- env-qa
|
||||
maxUpdate: 0 # if 0, no matched applications will be synced unless they're synced manually
|
||||
- matchExpressions:
|
||||
- key: envLabel
|
||||
operator: In
|
||||
values:
|
||||
- env-prod
|
||||
maxUpdate: 10% # maxUpdate supports both integer and percentage string values (rounds down, but floored at 1 Application for >0%)
|
||||
|
||||
ignoreApplicationDifferences:
|
||||
- jsonPointers:
|
||||
- /spec/source/targetRevision
|
||||
@@ -36,3 +221,94 @@ spec:
|
||||
jqPathExpressions:
|
||||
- .spec.source.helm.values
|
||||
|
||||
# Cluster-decision-resource-based ApplicationSet generator
|
||||
- clusterDecisionResource:
|
||||
# ConfigMap with GVK information for the duck type resource
|
||||
configMapRef: my-configmap
|
||||
name: quak # Choose either "name" of the resource or "labelSelector"
|
||||
labelSelector:
|
||||
matchLabels: # OPTIONAL
|
||||
duck: spotted
|
||||
matchExpressions: # OPTIONAL
|
||||
- key: duck
|
||||
operator: In
|
||||
values:
|
||||
- "spotted"
|
||||
- "canvasback"
|
||||
# OPTIONAL: Checks for changes every 60sec (default 3min)
|
||||
requeueAfterSeconds: 60
|
||||
|
||||
# The Pull Request generator uses the API of an SCMaaS provider to automatically discover open pull requests within a repository
|
||||
- pullRequest:
|
||||
# When using a Pull Request generator, the ApplicationSet controller polls every `requeueAfterSeconds` interval (defaulting to every 30 minutes) to detect changes.
|
||||
requeueAfterSeconds: 1800
|
||||
# See below for provider specific options.
|
||||
# Specify the repository from which to fetch the GitHub Pull requests.
|
||||
github:
|
||||
# The GitHub organization or user.
|
||||
owner: myorg
|
||||
# The Github repository
|
||||
repo: myrepository
|
||||
# For GitHub Enterprise (optional)
|
||||
api: https://git.example.com/
|
||||
# Reference to a Secret containing an access token. (optional)
|
||||
tokenRef:
|
||||
secretName: github-token
|
||||
key: token
|
||||
# (optional) use a GitHub App to access the API instead of a PAT.
|
||||
appSecretName: github-app-repo-creds
|
||||
# Labels is used to filter the PRs that you want to target. (optional)
|
||||
labels:
|
||||
- preview
|
||||
|
||||
# Filters allow selecting which pull requests to generate for
|
||||
# Include any pull request ending with "argocd". (optional)
|
||||
filters:
|
||||
- branchMatch: ".*-argocd"
|
||||
|
||||
# Specify the project from which to fetch the GitLab merge requests.
|
||||
gitlab:
|
||||
# Specify the repository from which to fetch the Gitea Pull requests.
|
||||
gitea:
|
||||
# Fetch pull requests from a repo hosted on a Bitbucket Server (not the same as Bitbucket Cloud).
|
||||
bitbucketServer:
|
||||
# Fetch pull requests from a repo hosted on a Bitbucket Cloud.
|
||||
bitbucket:
|
||||
# Specify the organization, project and repository from which you want to fetch pull requests.
|
||||
azuredevops:
|
||||
# Fetch pull requests from AWS CodeCommit repositories.
|
||||
awsCodeCommit:
|
||||
|
||||
# The list generator generates a set of two application which then filter by the key value to only select the env with value staging
|
||||
- list:
|
||||
elements:
|
||||
- cluster: engineering-dev
|
||||
url: https://kubernetes.default.svc
|
||||
env: staging
|
||||
- cluster: engineering-prod
|
||||
url: https://kubernetes.default.svc
|
||||
env: prod
|
||||
# The generator's template field takes precedence over the spec's template fields
|
||||
template:
|
||||
metadata: {}
|
||||
spec:
|
||||
project: "default"
|
||||
source:
|
||||
revision: HEAD
|
||||
repoURL: https://github.com/argoproj/argo-cd.git
|
||||
# New path value is generated here:
|
||||
path: 'applicationset/examples/template-override/{{cluster}}-override'
|
||||
destination: {}
|
||||
|
||||
selector:
|
||||
matchLabels:
|
||||
env: staging
|
||||
# It is also possible to use matchExpressions for more powerful selectors
|
||||
- clusters: {}
|
||||
selector:
|
||||
matchExpressions:
|
||||
- key: server
|
||||
operator: In
|
||||
values:
|
||||
- https://kubernetes.default.svc
|
||||
- https://some-other-cluster
|
||||
@@ -72,7 +72,7 @@ data:
|
||||
The allow-list only applies to SCM providers for which the user may configure a custom `api`. Where an SCM or PR
|
||||
generator does not accept a custom API URL, the provider is implicitly allowed.
|
||||
|
||||
If you do not intend to allow users to use the SCM or PR generators, you can disable them entirely by setting the environment variable `ARGOCD_APPLICATIONSET_CONTROLLER_ALLOW_SCM_PROVIDERS` to argocd-cmd-params-cm `applicationsetcontroller.allow.scm.providers` to `false`.
|
||||
If you do not intend to allow users to use the SCM or PR generators, you can disable them entirely by setting the environment variable `ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_SCM_PROVIDERS` to argocd-cmd-params-cm `applicationsetcontroller.enable.scm.providers` to `false`.
|
||||
|
||||
### Overview
|
||||
|
||||
|
||||
@@ -136,6 +136,29 @@ However, if you do wish to target both local and non-local clusters, while also
|
||||
|
||||
These steps might seem counterintuitive, but the act of changing one of the default values for the local cluster causes the Argo CD Web UI to create a new secret for this cluster. In the Argo CD namespace, you should now see a Secret resource named `cluster-(cluster suffix)` with label `argocd.argoproj.io/secret-type": "cluster"`. You may also create a local [cluster secret declaratively](../../declarative-setup/#clusters), or with the CLI using `argocd cluster add "(context name)" --in-cluster`, rather than through the Web UI.
|
||||
|
||||
### Fetch clusters based on their K8s version
|
||||
|
||||
There is also the possibility to fetch clusters based upon their Kubernetes version. To do this, the label `argocd.argoproj.io/auto-label-cluster-info` needs to be set to `true` on the cluster secret.
|
||||
Once that has been set, the controller will dynamically label the cluster secret with the Kubernetes version it is running on. To retrieve that value, you need to use the
|
||||
`argocd.argoproj.io/kubernetes-version`, as the example below demonstrates:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
goTemplate: true
|
||||
generators:
|
||||
- clusters:
|
||||
selector:
|
||||
matchLabels:
|
||||
argocd.argoproj.io/kubernetes-version: 1.28
|
||||
# matchExpressions are also supported.
|
||||
#matchExpressions:
|
||||
# - key: argocd.argoproj.io/kubernetes-version
|
||||
# operator: In
|
||||
# values:
|
||||
# - "1.27"
|
||||
# - "1.28"
|
||||
```
|
||||
|
||||
### Pass additional key-value pairs via `values` field
|
||||
|
||||
You may pass additional, arbitrary string key-value pairs via the `values` field of the cluster generator. Values added via the `values` field are added as `values.(field)`
|
||||
|
||||
@@ -53,7 +53,7 @@ It can be enabled in any of these ways:
|
||||
|
||||
1. Pass `--enable-new-git-file-globbing` to the ApplicationSet controller args.
|
||||
1. Set `ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_NEW_GIT_FILE_GLOBBING=true` in the ApplicationSet controller environment variables.
|
||||
1. Set `applicationsetcontroller.enable.new.git.file.globbing: true` in the Argo CD ConfigMap.
|
||||
1. Set `applicationsetcontroller.enable.new.git.file.globbing: "true"` in the `argocd-cmd-params-cm` ConfigMap.
|
||||
|
||||
Note that the default may change in the future.
|
||||
|
||||
|
||||
@@ -84,8 +84,8 @@ spec:
|
||||
generators:
|
||||
- pullRequest:
|
||||
gitlab:
|
||||
# The GitLab project.
|
||||
project: myproject
|
||||
# The GitLab project ID.
|
||||
project: "12341234"
|
||||
# For self-hosted GitLab (optional)
|
||||
api: https://git.example.com/
|
||||
# Reference to a Secret containing an access token. (optional)
|
||||
@@ -104,7 +104,7 @@ spec:
|
||||
# ...
|
||||
```
|
||||
|
||||
* `project`: Required name of the GitLab project.
|
||||
* `project`: Required project ID of the GitLab project.
|
||||
* `api`: If using self-hosted GitLab, the URL to access it. (Optional)
|
||||
* `tokenRef`: A `Secret` name and key containing the GitLab 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 MRs that you want to target. (Optional)
|
||||
|
||||