Compare commits
277 Commits
v2.10.2
...
update-ver
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b28d4dbbd | ||
|
|
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 }}
|
||||
|
||||
12
.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 }}
|
||||
@@ -128,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"
|
||||
@@ -147,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 }}
|
||||
@@ -212,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"
|
||||
@@ -230,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
|
||||
|
||||
@@ -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
|
||||
|
||||
10
Dockerfile
@@ -4,7 +4,7 @@ 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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
100
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
|
||||
@@ -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:
|
||||
|
||||
@@ -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/)
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ header:
|
||||
expiration-date: '2024-10-31T00:00:00.000Z' # One year from initial release.
|
||||
last-updated: '2023-10-27'
|
||||
last-reviewed: '2023-10-27'
|
||||
commit-hash: b71277c6beb949d0199d647a582bc25822b88838
|
||||
commit-hash: 2.12.0
|
||||
project-url: https://github.com/argoproj/argo-cd
|
||||
project-release: v2.9.0-rc3
|
||||
project-release: v2.12.0
|
||||
changelog: https://github.com/argoproj/argo-cd/releases
|
||||
license: https://github.com/argoproj/argo-cd/blob/master/LICENSE
|
||||
project-lifecycle:
|
||||
|
||||
21
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)
|
||||
@@ -44,14 +46,14 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
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/)
|
||||
@@ -94,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/)
|
||||
@@ -112,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)
|
||||
@@ -126,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/)
|
||||
@@ -186,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)
|
||||
@@ -196,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)
|
||||
@@ -212,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/)
|
||||
@@ -219,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)
|
||||
@@ -234,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/)
|
||||
@@ -253,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/)
|
||||
@@ -268,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/)
|
||||
@@ -281,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/)
|
||||
|
||||
@@ -124,18 +124,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 +311,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{
|
||||
|
||||
@@ -2423,6 +2423,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)
|
||||
|
||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 27 KiB |
@@ -5664,6 +5664,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 +6424,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 +7413,7 @@
|
||||
"properties": {
|
||||
"elements": {
|
||||
"type": "array",
|
||||
"title": "+kubebuilder:validation:Optional",
|
||||
"items": {
|
||||
"$ref": "#/definitions/v1JSON"
|
||||
}
|
||||
@@ -8499,6 +8508,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,6 +19,7 @@ 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"
|
||||
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
|
||||
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
|
||||
@@ -220,7 +220,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)")
|
||||
@@ -229,8 +229,10 @@ 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
|
||||
cacheSource = appstatecache.AddCacheFlagsToCmd(&command, cacheutil.Options{
|
||||
OnClientCreated: func(client *redis.Client) {
|
||||
redisClient = client
|
||||
},
|
||||
})
|
||||
return &command
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -19,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"
|
||||
@@ -66,6 +68,7 @@ func NewCommand() *cobra.Command {
|
||||
enableGZip bool
|
||||
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
|
||||
cacheSrc func() (*servercache.Cache, error)
|
||||
repoServerCacheSrc func() (*reposervercache.Cache, error)
|
||||
frameOptions string
|
||||
contentSecurityPolicy string
|
||||
repoServerPlaintext bool
|
||||
@@ -107,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)
|
||||
|
||||
@@ -191,6 +196,7 @@ func NewCommand() *cobra.Command {
|
||||
EnableGZip: enableGZip,
|
||||
TLSConfigCustomizer: tlsConfigCustomizer,
|
||||
Cache: cache,
|
||||
RepoServerCache: repoServerCache,
|
||||
XFrameOptions: frameOptions,
|
||||
ContentSecurityPolicy: contentSecurityPolicy,
|
||||
RedisClient: redisClient,
|
||||
@@ -263,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
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ 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"
|
||||
@@ -269,18 +270,26 @@ 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)
|
||||
errors.CheckError(err)
|
||||
} else {
|
||||
@@ -437,5 +446,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())
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"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"
|
||||
argoappv1 "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"
|
||||
@@ -72,7 +71,7 @@ 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
|
||||
@@ -87,8 +86,12 @@ 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)
|
||||
clusterShardingCache.Init(clustersList, appItems)
|
||||
clusterShards := clusterShardingCache.GetDistribution()
|
||||
|
||||
var cache *appstatecache.Cache
|
||||
@@ -114,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)
|
||||
@@ -130,12 +129,6 @@ func loadClusters(ctx context.Context, kubeClient *kubernetes.Clientset, appClie
|
||||
|
||||
batchSize := 10
|
||||
batchesCount := int(math.Ceil(float64(len(clusters)) / float64(batchSize)))
|
||||
clusterSharding := &sharding.ClusterSharding{
|
||||
Shard: shard,
|
||||
Replicas: replicas,
|
||||
Shards: make(map[string]int),
|
||||
Clusters: make(map[string]*v1alpha1.Cluster),
|
||||
}
|
||||
for batchNum := 0; batchNum < batchesCount; batchNum++ {
|
||||
batchStart := batchSize * batchNum
|
||||
batchEnd := batchSize * (batchNum + 1)
|
||||
@@ -147,9 +140,7 @@ func loadClusters(ctx context.Context, kubeClient *kubernetes.Clientset, appClie
|
||||
clusterShard := 0
|
||||
cluster := batch[i]
|
||||
if replicas > 0 {
|
||||
distributionFunction := sharding.GetDistributionFunction(clusterSharding.GetClusterAccessor(), common.DefaultShardingAlgorithm, replicas)
|
||||
distributionFunction(&cluster)
|
||||
clusterShard := clusterShards[cluster.Server]
|
||||
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)
|
||||
}
|
||||
@@ -626,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,
|
||||
@@ -658,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.
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
k8swatch "k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
@@ -92,6 +93,8 @@ func NewApplicationCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
|
||||
command.AddCommand(NewApplicationResourceActionsCommand(clientOpts))
|
||||
command.AddCommand(NewApplicationListResourcesCommand(clientOpts))
|
||||
command.AddCommand(NewApplicationLogsCommand(clientOpts))
|
||||
command.AddCommand(NewApplicationAddSourceCommand(clientOpts))
|
||||
command.AddCommand(NewApplicationRemoveSourceCommand(clientOpts))
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -101,6 +104,7 @@ type watchOpts struct {
|
||||
operation bool
|
||||
suspended bool
|
||||
degraded bool
|
||||
delete bool
|
||||
}
|
||||
|
||||
// NewApplicationCreateCommand returns a new instance of an `argocd app create` command
|
||||
@@ -133,13 +137,15 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
|
||||
# Create a Kustomize app
|
||||
argocd app create kustomize-guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path kustomize-guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --kustomize-image gcr.io/heptio-images/ks-guestbook-demo:0.1
|
||||
|
||||
# Create a MultiSource app while yaml file contains an application with multiple sources
|
||||
argocd app create guestbook --file <path-to-yaml-file>
|
||||
|
||||
# Create a app using a custom tool:
|
||||
argocd app create kasane --repo https://github.com/argoproj/argocd-example-apps.git --path plugins/kasane --dest-namespace default --dest-server https://kubernetes.default.svc --config-management-plugin kasane`,
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
ctx := c.Context()
|
||||
|
||||
argocdClient := headless.NewClientOrDie(clientOpts, c)
|
||||
|
||||
apps, err := cmdutil.ConstructApps(fileURL, appName, labels, annotations, args, appOpts, c.Flags())
|
||||
errors.CheckError(err)
|
||||
|
||||
@@ -301,7 +307,7 @@ func printHeader(acdClient argocdclient.Client, app *argoappv1.Application, ctx
|
||||
fmt.Println()
|
||||
printOperationResult(app.Status.OperationState)
|
||||
}
|
||||
if showParams {
|
||||
if !app.Spec.HasMultipleSources() && showParams {
|
||||
printParams(app)
|
||||
}
|
||||
}
|
||||
@@ -314,6 +320,7 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
|
||||
output string
|
||||
showParams bool
|
||||
showOperation bool
|
||||
appNamespace string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "get APPNAME",
|
||||
@@ -357,7 +364,7 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
|
||||
conn, appIf := acdClient.NewApplicationClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], "")
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
|
||||
|
||||
app, err := appIf.Get(ctx, &application.ApplicationQuery{
|
||||
Name: &appName,
|
||||
@@ -410,6 +417,7 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
|
||||
command.Flags().BoolVar(&showParams, "show-params", false, "Show application parameters and overrides")
|
||||
command.Flags().BoolVar(&refresh, "refresh", false, "Refresh application data when retrieving")
|
||||
command.Flags().BoolVar(&hardRefresh, "hard-refresh", false, "Refresh application data as well as target manifests cache")
|
||||
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Only get application from namespace")
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -545,16 +553,19 @@ func NewApplicationLogsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
}
|
||||
|
||||
func printAppSummaryTable(app *argoappv1.Application, appURL string, windows *argoappv1.SyncWindows) {
|
||||
source := app.Spec.GetSource()
|
||||
fmt.Printf(printOpFmtStr, "Name:", app.QualifiedName())
|
||||
fmt.Printf(printOpFmtStr, "Project:", app.Spec.GetProject())
|
||||
fmt.Printf(printOpFmtStr, "Server:", getServer(app))
|
||||
fmt.Printf(printOpFmtStr, "Namespace:", app.Spec.Destination.Namespace)
|
||||
fmt.Printf(printOpFmtStr, "URL:", appURL)
|
||||
fmt.Printf(printOpFmtStr, "Repo:", source.RepoURL)
|
||||
fmt.Printf(printOpFmtStr, "Target:", source.TargetRevision)
|
||||
fmt.Printf(printOpFmtStr, "Path:", source.Path)
|
||||
printAppSourceDetails(&source)
|
||||
if !app.Spec.HasMultipleSources() {
|
||||
fmt.Println("Source:")
|
||||
} else {
|
||||
fmt.Println("Sources:")
|
||||
}
|
||||
for _, source := range app.Spec.GetSources() {
|
||||
printAppSourceDetails(&source)
|
||||
}
|
||||
var wds []string
|
||||
var status string
|
||||
var allow, deny, inactiveAllows bool
|
||||
@@ -602,7 +613,7 @@ func printAppSummaryTable(app *argoappv1.Application, appURL string, windows *ar
|
||||
syncPolicy += " (Prune)"
|
||||
}
|
||||
} else {
|
||||
syncPolicy = "<none>"
|
||||
syncPolicy = "Manual"
|
||||
}
|
||||
fmt.Printf(printOpFmtStr, "Sync Policy:", syncPolicy)
|
||||
syncStatusStr := string(app.Status.Sync.Status)
|
||||
@@ -624,11 +635,19 @@ func printAppSummaryTable(app *argoappv1.Application, appURL string, windows *ar
|
||||
}
|
||||
|
||||
func printAppSourceDetails(appSrc *argoappv1.ApplicationSource) {
|
||||
fmt.Printf(printOpFmtStr, "- Repo:", appSrc.RepoURL)
|
||||
fmt.Printf(printOpFmtStr, " Target:", appSrc.TargetRevision)
|
||||
if appSrc.Path != "" {
|
||||
fmt.Printf(printOpFmtStr, " Path:", appSrc.Path)
|
||||
}
|
||||
if appSrc.Ref != "" {
|
||||
fmt.Printf(printOpFmtStr, " Ref:", appSrc.Ref)
|
||||
}
|
||||
if appSrc.Helm != nil && len(appSrc.Helm.ValueFiles) > 0 {
|
||||
fmt.Printf(printOpFmtStr, "Helm Values:", strings.Join(appSrc.Helm.ValueFiles, ","))
|
||||
fmt.Printf(printOpFmtStr, " Helm Values:", strings.Join(appSrc.Helm.ValueFiles, ","))
|
||||
}
|
||||
if appSrc.Kustomize != nil && appSrc.Kustomize.NamePrefix != "" {
|
||||
fmt.Printf(printOpFmtStr, "Name Prefix:", appSrc.Kustomize.NamePrefix)
|
||||
fmt.Printf(printOpFmtStr, " Name Prefix:", appSrc.Kustomize.NamePrefix)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -711,7 +730,9 @@ func getServer(app *argoappv1.Application) string {
|
||||
// NewApplicationSetCommand returns a new instance of an `argocd app set` command
|
||||
func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
appOpts cmdutil.AppOptions
|
||||
appOpts cmdutil.AppOptions
|
||||
appNamespace string
|
||||
sourceIndex int
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "set APPNAME",
|
||||
@@ -729,6 +750,9 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
|
||||
# Set and override application parameters with a parameter file
|
||||
argocd app set my-app --parameter-file path/to/parameter-file.yaml
|
||||
|
||||
# Set and override application parameters for a source at index 1 under spec.sources of app my-app. source-index starts at 1.
|
||||
argocd app set my-app --source-index 1 --repo https://github.com/argoproj/argocd-example-apps.git
|
||||
|
||||
# Set application parameters and specify the namespace
|
||||
argocd app set my-app --parameter key1=value1 --parameter key2=value2 --namespace my-namespace
|
||||
`),
|
||||
@@ -740,21 +764,32 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], "")
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
|
||||
argocdClient := headless.NewClientOrDie(clientOpts, c)
|
||||
conn, appIf := argocdClient.NewApplicationClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
app, err := appIf.Get(ctx, &application.ApplicationQuery{Name: &appName, AppNamespace: &appNs})
|
||||
errors.CheckError(err)
|
||||
|
||||
visited := cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts)
|
||||
if app.Spec.HasMultipleSources() {
|
||||
if sourceIndex <= 0 {
|
||||
errors.CheckError(fmt.Errorf("Source index should be specified and greater than 0 for applications with multiple sources"))
|
||||
}
|
||||
if len(app.Spec.GetSources()) < sourceIndex {
|
||||
errors.CheckError(fmt.Errorf("Source index should be less than the number of sources in the application"))
|
||||
}
|
||||
}
|
||||
|
||||
// sourceIndex startes with 1, thus, it needs to be decreased by 1 to find the correct index in the list of sources
|
||||
sourceIndex = sourceIndex - 1
|
||||
visited := cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts, sourceIndex)
|
||||
if visited == 0 {
|
||||
log.Error("Please set at least one option to update")
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
setParameterOverrides(app, appOpts.Parameters)
|
||||
setParameterOverrides(app, appOpts.Parameters, sourceIndex)
|
||||
_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
|
||||
Name: &app.Name,
|
||||
Spec: &app.Spec,
|
||||
@@ -764,7 +799,9 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
|
||||
errors.CheckError(err)
|
||||
},
|
||||
}
|
||||
command.Flags().IntVar(&sourceIndex, "source-index", -1, "Index of the source from the list of sources of the app. Index starts at 1.")
|
||||
cmdutil.AddAppFlags(command, &appOpts)
|
||||
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Set application parameters in namespace")
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -782,6 +819,7 @@ type unsetOpts struct {
|
||||
ignoreMissingValueFiles bool
|
||||
pluginEnvs []string
|
||||
passCredentials bool
|
||||
ref bool
|
||||
}
|
||||
|
||||
// IsZero returns true when the Application options for kustomize are considered empty
|
||||
@@ -797,17 +835,24 @@ func (o *unsetOpts) KustomizeIsZero() bool {
|
||||
|
||||
// NewApplicationUnsetCommand returns a new instance of an `argocd app unset` command
|
||||
func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
sourceIndex int
|
||||
)
|
||||
appOpts := cmdutil.AppOptions{}
|
||||
opts := unsetOpts{}
|
||||
var appNamespace string
|
||||
var command = &cobra.Command{
|
||||
Use: "unset APPNAME parameters",
|
||||
Short: "Unset application parameters",
|
||||
Example: ` # Unset kustomize override kustomize image
|
||||
argocd app unset my-app --kustomize-image=alpine
|
||||
|
||||
# Unset kustomize override prefix
|
||||
# Unset kustomize override suffix
|
||||
argocd app unset my-app --namesuffix
|
||||
|
||||
# Unset kustomize override suffix for source at index 1 under spec.sources of app my-app. source-index starts at 1.
|
||||
argocd app unset my-app --source-index 1 --namesuffix
|
||||
|
||||
# Unset parameter override
|
||||
argocd app unset my-app -p COMPONENT=PARAM`,
|
||||
|
||||
@@ -818,14 +863,25 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], "")
|
||||
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
|
||||
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
app, err := appIf.Get(ctx, &application.ApplicationQuery{Name: &appName, AppNamespace: &appNs})
|
||||
errors.CheckError(err)
|
||||
|
||||
source := app.Spec.GetSource()
|
||||
updated, nothingToUnset := unset(&source, opts)
|
||||
if app.Spec.HasMultipleSources() {
|
||||
if sourceIndex <= 0 {
|
||||
errors.CheckError(fmt.Errorf("Source index should be specified and greater than 0 for applications with multiple sources"))
|
||||
}
|
||||
if len(app.Spec.GetSources()) < sourceIndex {
|
||||
errors.CheckError(fmt.Errorf("Source index should be less than the number of sources in the application"))
|
||||
}
|
||||
}
|
||||
|
||||
source := app.Spec.GetSourcePtr(sourceIndex)
|
||||
|
||||
updated, nothingToUnset := unset(source, opts)
|
||||
if nothingToUnset {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
@@ -834,7 +890,7 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
|
||||
return
|
||||
}
|
||||
|
||||
cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts)
|
||||
cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts, sourceIndex)
|
||||
_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
|
||||
Name: &app.Name,
|
||||
Spec: &app.Spec,
|
||||
@@ -844,6 +900,7 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
|
||||
errors.CheckError(err)
|
||||
},
|
||||
}
|
||||
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Unset application parameters in namespace")
|
||||
command.Flags().StringArrayVarP(&opts.parameters, "parameter", "p", []string{}, "Unset a parameter override (e.g. -p guestbook=image)")
|
||||
command.Flags().StringArrayVar(&opts.valuesFiles, "values", []string{}, "Unset one or more Helm values files")
|
||||
command.Flags().BoolVar(&opts.valuesLiteral, "values-literal", false, "Unset literal Helm values block")
|
||||
@@ -856,13 +913,22 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
|
||||
command.Flags().StringArrayVar(&opts.kustomizeReplicas, "kustomize-replica", []string{}, "Kustomize replicas name (e.g. --kustomize-replica my-deployment --kustomize-replica my-statefulset)")
|
||||
command.Flags().StringArrayVar(&opts.pluginEnvs, "plugin-env", []string{}, "Unset plugin env variables (e.g --plugin-env name)")
|
||||
command.Flags().BoolVar(&opts.passCredentials, "pass-credentials", false, "Unset passCredentials")
|
||||
command.Flags().BoolVar(&opts.ref, "ref", false, "Unset ref on the source")
|
||||
command.Flags().IntVar(&sourceIndex, "source-index", -1, "Index of the source from the list of sources of the app. Index starts at 1.")
|
||||
return command
|
||||
}
|
||||
|
||||
func unset(source *argoappv1.ApplicationSource, opts unsetOpts) (updated bool, nothingToUnset bool) {
|
||||
needToUnsetRef := false
|
||||
if opts.ref && source.Ref != "" {
|
||||
source.Ref = ""
|
||||
updated = true
|
||||
needToUnsetRef = true
|
||||
}
|
||||
|
||||
if source.Kustomize != nil {
|
||||
if opts.KustomizeIsZero() {
|
||||
return false, true
|
||||
return updated, !needToUnsetRef
|
||||
}
|
||||
|
||||
if opts.namePrefix && source.Kustomize.NamePrefix != "" {
|
||||
@@ -912,7 +978,7 @@ func unset(source *argoappv1.ApplicationSource, opts unsetOpts) (updated bool, n
|
||||
}
|
||||
if source.Helm != nil {
|
||||
if len(opts.parameters) == 0 && len(opts.valuesFiles) == 0 && !opts.valuesLiteral && !opts.ignoreMissingValueFiles && !opts.passCredentials {
|
||||
return false, true
|
||||
return updated, !needToUnsetRef
|
||||
}
|
||||
for _, paramStr := range opts.parameters {
|
||||
helmParams := source.Helm.Parameters
|
||||
@@ -949,9 +1015,10 @@ func unset(source *argoappv1.ApplicationSource, opts unsetOpts) (updated bool, n
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
|
||||
if source.Plugin != nil {
|
||||
if len(opts.pluginEnvs) == 0 {
|
||||
return false, true
|
||||
return false, !needToUnsetRef
|
||||
}
|
||||
for _, env := range opts.pluginEnvs {
|
||||
err := source.Plugin.RemoveEnvEntry(env)
|
||||
@@ -1057,12 +1124,15 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
localRepoRoot string
|
||||
serverSideGenerate bool
|
||||
localIncludes []string
|
||||
appNamespace string
|
||||
revisions []string
|
||||
sourceIndexes []int64
|
||||
)
|
||||
shortDesc := "Perform a diff against the target and live state."
|
||||
var command = &cobra.Command{
|
||||
Use: "diff APPNAME",
|
||||
Short: shortDesc,
|
||||
Long: shortDesc + "\nUses 'diff' to render the difference. KUBECTL_EXTERNAL_DIFF environment variable can be used to select your own diff tool.\nReturns the following exit codes: 2 on general errors, 1 when a diff is found, and 0 when no diff is found",
|
||||
Long: shortDesc + "\nUses 'diff' to render the difference. KUBECTL_EXTERNAL_DIFF environment variable can be used to select your own diff tool.\nReturns the following exit codes: 2 on general errors, 1 when a diff is found, and 0 when no diff is found\nKubernetes Secrets are ignored from this diff.",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
ctx := c.Context()
|
||||
|
||||
@@ -1070,10 +1140,15 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
if len(revisions) != len(sourceIndexes) {
|
||||
errors.CheckError(fmt.Errorf("While using revisions and source-indexes, length of values for both flags should be same."))
|
||||
}
|
||||
|
||||
clientset := headless.NewClientOrDie(clientOpts, c)
|
||||
conn, appIf := clientset.NewApplicationClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], "")
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
|
||||
app, err := appIf.Get(ctx, &application.ApplicationQuery{
|
||||
Name: &appName,
|
||||
Refresh: getRefreshType(refresh, hardRefresh),
|
||||
@@ -1088,7 +1163,27 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
argoSettings, err := settingsIf.Get(ctx, &settings.SettingsQuery{})
|
||||
errors.CheckError(err)
|
||||
diffOption := &DifferenceOption{}
|
||||
if revision != "" {
|
||||
if app.Spec.HasMultipleSources() && len(revisions) > 0 && len(sourceIndexes) > 0 {
|
||||
|
||||
revisionSourceMappings := make(map[int64]string, 0)
|
||||
for i, index := range sourceIndexes {
|
||||
if index <= 0 {
|
||||
errors.CheckError(fmt.Errorf("source-index cannot be less than or equal to 0. Index starts at 1."))
|
||||
}
|
||||
revisionSourceMappings[index] = revisions[i]
|
||||
}
|
||||
|
||||
q := application.ApplicationManifestQuery{
|
||||
Name: &appName,
|
||||
AppNamespace: &appNs,
|
||||
RevisionSourceMappings: revisionSourceMappings,
|
||||
}
|
||||
res, err := appIf.GetManifests(ctx, &q)
|
||||
errors.CheckError(err)
|
||||
|
||||
diffOption.res = res
|
||||
diffOption.revisionSourceMappings = &revisionSourceMappings
|
||||
} else if revision != "" {
|
||||
q := application.ApplicationManifestQuery{
|
||||
Name: &appName,
|
||||
Revision: &revision,
|
||||
@@ -1137,17 +1232,21 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
command.Flags().StringVar(&localRepoRoot, "local-repo-root", "/", "Path to the repository root. Used together with --local allows setting the repository root")
|
||||
command.Flags().BoolVar(&serverSideGenerate, "server-side-generate", false, "Used with --local, this will send your manifests to the server for diffing")
|
||||
command.Flags().StringArrayVar(&localIncludes, "local-include", []string{"*.yaml", "*.yml", "*.json"}, "Used with --server-side-generate, specify patterns of filenames to send. Matching is based on filename and not path.")
|
||||
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Only render the difference in namespace")
|
||||
command.Flags().StringArrayVar(&revisions, "revisions", []string{}, "Show manifests at specific revisions for the index of sources in source-indexes")
|
||||
command.Flags().Int64SliceVar(&sourceIndexes, "source-indexes", []int64{}, "List of source indexes. Default is empty array. Indexes start at 1.")
|
||||
return command
|
||||
}
|
||||
|
||||
// DifferenceOption struct to store diff options
|
||||
type DifferenceOption struct {
|
||||
local string
|
||||
localRepoRoot string
|
||||
revision string
|
||||
cluster *argoappv1.Cluster
|
||||
res *repoapiclient.ManifestResponse
|
||||
serversideRes *repoapiclient.ManifestResponse
|
||||
local string
|
||||
localRepoRoot string
|
||||
revision string
|
||||
cluster *argoappv1.Cluster
|
||||
res *repoapiclient.ManifestResponse
|
||||
serversideRes *repoapiclient.ManifestResponse
|
||||
revisionSourceMappings *map[int64]string
|
||||
}
|
||||
|
||||
// findandPrintDiff ... Prints difference between application current state and state stored in git or locally, returns boolean as true if difference is found else returns false
|
||||
@@ -1159,7 +1258,7 @@ func findandPrintDiff(ctx context.Context, app *argoappv1.Application, proj *arg
|
||||
if diffOptions.local != "" {
|
||||
localObjs := groupObjsByKey(getLocalObjects(ctx, app, proj, diffOptions.local, diffOptions.localRepoRoot, argoSettings.AppLabelKey, diffOptions.cluster.Info.ServerVersion, diffOptions.cluster.Info.APIVersions, argoSettings.KustomizeOptions, argoSettings.TrackingMethod), liveObjs, app.Spec.Destination.Namespace)
|
||||
items = groupObjsForDiff(resources, localObjs, items, argoSettings, app.InstanceName(argoSettings.ControllerNamespace), app.Spec.Destination.Namespace)
|
||||
} else if diffOptions.revision != "" {
|
||||
} else if diffOptions.revision != "" || (diffOptions.revisionSourceMappings != nil) {
|
||||
var unstructureds []*unstructured.Unstructured
|
||||
for _, mfst := range diffOptions.res.Manifests {
|
||||
obj, err := argoappv1.UnmarshalToUnstructured(mfst)
|
||||
@@ -1277,6 +1376,8 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
|
||||
noPrompt bool
|
||||
propagationPolicy string
|
||||
selector string
|
||||
wait bool
|
||||
appNamespace string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "delete APPNAME",
|
||||
@@ -1300,7 +1401,8 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
|
||||
acdClient := headless.NewClientOrDie(clientOpts, c)
|
||||
conn, appIf := acdClient.NewApplicationClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
var isTerminal bool = isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
|
||||
var isConfirmAll bool = false
|
||||
@@ -1318,7 +1420,7 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
|
||||
}
|
||||
|
||||
for _, appFullName := range appNames {
|
||||
appName, appNs := argo.ParseFromQualifiedName(appFullName, "")
|
||||
appName, appNs := argo.ParseFromQualifiedName(appFullName, appNamespace)
|
||||
appDeleteReq := application.ApplicationDeleteRequest{
|
||||
Name: &appName,
|
||||
AppNamespace: &appNs,
|
||||
@@ -1347,6 +1449,9 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
|
||||
if lowercaseAnswer == "y" {
|
||||
_, err := appIf.Delete(ctx, &appDeleteReq)
|
||||
errors.CheckError(err)
|
||||
if wait {
|
||||
checkForDeleteEvent(ctx, acdClient, appFullName)
|
||||
}
|
||||
fmt.Printf("application '%s' deleted\n", appFullName)
|
||||
} else {
|
||||
fmt.Println("The command to delete '" + appFullName + "' was cancelled.")
|
||||
@@ -1354,6 +1459,10 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
|
||||
} else {
|
||||
_, err := appIf.Delete(ctx, &appDeleteReq)
|
||||
errors.CheckError(err)
|
||||
|
||||
if wait {
|
||||
checkForDeleteEvent(ctx, acdClient, appFullName)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1362,9 +1471,20 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
|
||||
command.Flags().StringVarP(&propagationPolicy, "propagation-policy", "p", "foreground", "Specify propagation policy for deletion of application's resources. One of: foreground|background")
|
||||
command.Flags().BoolVarP(&noPrompt, "yes", "y", false, "Turn off prompting to confirm cascaded deletion of application resources")
|
||||
command.Flags().StringVarP(&selector, "selector", "l", "", "Delete all apps with matching label. Supports '=', '==', '!=', in, notin, exists & not exists. Matching apps must satisfy all of the specified label constraints.")
|
||||
command.Flags().BoolVar(&wait, "wait", false, "Wait until deletion of the application(s) completes")
|
||||
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Namespace where the application will be deleted from")
|
||||
return command
|
||||
}
|
||||
|
||||
func checkForDeleteEvent(ctx context.Context, acdClient argocdclient.Client, appFullName string) {
|
||||
appEventCh := acdClient.WatchApplicationWithRetry(ctx, appFullName, "")
|
||||
for appEvent := range appEventCh {
|
||||
if appEvent.Type == k8swatch.Deleted {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Print simple list of application names
|
||||
func printApplicationNames(apps []argoappv1.Application) {
|
||||
for _, app := range apps {
|
||||
@@ -1472,7 +1592,7 @@ func NewApplicationListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
|
||||
func formatSyncPolicy(app argoappv1.Application) string {
|
||||
if app.Spec.SyncPolicy == nil || app.Spec.SyncPolicy.Automated == nil {
|
||||
return "<none>"
|
||||
return "Manual"
|
||||
}
|
||||
policy := "Auto"
|
||||
if app.Spec.SyncPolicy.Automated.Prune {
|
||||
@@ -1576,11 +1696,12 @@ func getWatchOpts(watch watchOpts) watchOpts {
|
||||
// NewApplicationWaitCommand returns a new instance of an `argocd app wait` command
|
||||
func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
watch watchOpts
|
||||
timeout uint
|
||||
selector string
|
||||
resources []string
|
||||
output string
|
||||
watch watchOpts
|
||||
timeout uint
|
||||
selector string
|
||||
resources []string
|
||||
output string
|
||||
appNamespace string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "wait [APPNAME.. | -l selector]",
|
||||
@@ -1625,10 +1746,14 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
list, err := appIf.List(ctx, &application.ApplicationQuery{Selector: pointer.String(selector)})
|
||||
errors.CheckError(err)
|
||||
for _, i := range list.Items {
|
||||
appNames = append(appNames, i.Name)
|
||||
appNames = append(appNames, i.QualifiedName())
|
||||
}
|
||||
}
|
||||
for _, appName := range appNames {
|
||||
// Construct QualifiedName
|
||||
if appNamespace != "" && !strings.Contains(appName, "/") {
|
||||
appName = appNamespace + "/" + appName
|
||||
}
|
||||
_, _, err := waitOnApplicationStatus(ctx, acdClient, appName, timeout, watch, selectedResources, output)
|
||||
errors.CheckError(err)
|
||||
}
|
||||
@@ -1638,10 +1763,12 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
command.Flags().BoolVar(&watch.health, "health", false, "Wait for health")
|
||||
command.Flags().BoolVar(&watch.suspended, "suspended", false, "Wait for suspended")
|
||||
command.Flags().BoolVar(&watch.degraded, "degraded", false, "Wait for degraded")
|
||||
command.Flags().BoolVar(&watch.delete, "delete", false, "Wait for delete")
|
||||
command.Flags().StringVarP(&selector, "selector", "l", "", "Wait for apps by label. Supports '=', '==', '!=', in, notin, exists & not exists. Matching apps must satisfy all of the specified label constraints.")
|
||||
command.Flags().StringArrayVar(&resources, "resource", []string{}, fmt.Sprintf("Sync only specific resources as GROUP%[1]sKIND%[1]sNAME or %[2]sGROUP%[1]sKIND%[1]sNAME. Fields may be blank and '*' can be used. This option may be specified repeatedly", resourceFieldDelimiter, resourceExcludeIndicator))
|
||||
command.Flags().BoolVar(&watch.operation, "operation", false, "Wait for pending operations")
|
||||
command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds")
|
||||
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Only wait for an application in namespace")
|
||||
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|tree|tree=detailed")
|
||||
return command
|
||||
}
|
||||
@@ -1699,6 +1826,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
diffChangesConfirm bool
|
||||
projects []string
|
||||
output string
|
||||
appNamespace string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "sync [APPNAME... | -l selector | --project project-name]",
|
||||
@@ -1743,7 +1871,10 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
|
||||
appNames := args
|
||||
if selector != "" || len(projects) > 0 {
|
||||
list, err := appIf.List(ctx, &application.ApplicationQuery{Selector: pointer.String(selector), Projects: projects})
|
||||
list, err := appIf.List(ctx, &application.ApplicationQuery{
|
||||
Selector: pointer.String(selector),
|
||||
AppNamespace: &appNamespace,
|
||||
Projects: projects})
|
||||
errors.CheckError(err)
|
||||
|
||||
// unlike list, we'd want to fail if nothing was found
|
||||
@@ -1764,6 +1895,10 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
}
|
||||
|
||||
for _, appQualifiedName := range appNames {
|
||||
// Construct QualifiedName
|
||||
if appNamespace != "" && !strings.Contains(appQualifiedName, "/") {
|
||||
appQualifiedName = appNamespace + "/" + appQualifiedName
|
||||
}
|
||||
appName, appNs := argo.ParseFromQualifiedName(appQualifiedName, "")
|
||||
|
||||
if len(selectedLabels) > 0 {
|
||||
@@ -1981,6 +2116,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
command.Flags().BoolVar(&diffChanges, "preview-changes", false, "Preview difference against the target and live state before syncing app and wait for user confirmation")
|
||||
command.Flags().StringArrayVar(&projects, "project", []string{}, "Sync apps that belong to the specified projects. This option may be specified repeatedly.")
|
||||
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|tree|tree=detailed")
|
||||
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Only sync an application in namespace")
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -1996,7 +2132,7 @@ func getAppNamesBySelector(ctx context.Context, appIf application.ApplicationSer
|
||||
return []string{}, fmt.Errorf("no apps match selector %v", selector)
|
||||
}
|
||||
for _, i := range list.Items {
|
||||
appNames = append(appNames, i.Name)
|
||||
appNames = append(appNames, i.QualifiedName())
|
||||
}
|
||||
}
|
||||
return appNames, nil
|
||||
@@ -2132,6 +2268,9 @@ func groupResourceStates(app *argoappv1.Application, selectedResources []*argoap
|
||||
|
||||
// check if resource health, sync and operation statuses matches watch options
|
||||
func checkResourceStatus(watch watchOpts, healthStatus string, syncStatus string, operationStatus *argoappv1.Operation) bool {
|
||||
if watch.delete {
|
||||
return false
|
||||
}
|
||||
healthCheckPassed := true
|
||||
|
||||
if watch.suspended && watch.health && watch.degraded {
|
||||
@@ -2284,6 +2423,12 @@ func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client,
|
||||
|
||||
finalOperationState = app.Status.OperationState
|
||||
operationInProgress := false
|
||||
|
||||
if watch.delete && appEvent.Type == k8swatch.Deleted {
|
||||
fmt.Printf("Application '%s' deleted\n", app.QualifiedName())
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// consider the operation is in progress
|
||||
if app.Operation != nil {
|
||||
// if it just got requested
|
||||
@@ -2350,11 +2495,11 @@ func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client,
|
||||
// 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.GetSourcePtr(index)
|
||||
var sourceType argoappv1.ApplicationSourceType
|
||||
if st, _ := source.ExplicitType(); st != nil {
|
||||
sourceType = *st
|
||||
@@ -2393,14 +2538,56 @@ func printApplicationHistoryIds(revHistory []argoappv1.RevisionHistory) {
|
||||
|
||||
// Print a history table for an application.
|
||||
func printApplicationHistoryTable(revHistory []argoappv1.RevisionHistory) {
|
||||
MAX_ALLOWED_REVISIONS := 7
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
_, _ = fmt.Fprintf(w, "ID\tDATE\tREVISION\n")
|
||||
type history struct {
|
||||
id int64
|
||||
date string
|
||||
revision string
|
||||
}
|
||||
varHistory := map[string][]history{}
|
||||
varHistoryKeys := []string{}
|
||||
for _, depInfo := range revHistory {
|
||||
rev := depInfo.Source.TargetRevision
|
||||
if len(depInfo.Revision) >= 7 {
|
||||
rev = fmt.Sprintf("%s (%s)", rev, depInfo.Revision[0:7])
|
||||
if depInfo.Sources != nil {
|
||||
for i, sourceInfo := range depInfo.Sources {
|
||||
rev := sourceInfo.TargetRevision
|
||||
if len(depInfo.Revisions) == len(depInfo.Sources) && len(depInfo.Revisions[i]) >= MAX_ALLOWED_REVISIONS {
|
||||
rev = fmt.Sprintf("%s (%s)", rev, depInfo.Revisions[i][0:MAX_ALLOWED_REVISIONS])
|
||||
}
|
||||
if _, ok := varHistory[sourceInfo.RepoURL]; !ok {
|
||||
varHistoryKeys = append(varHistoryKeys, sourceInfo.RepoURL)
|
||||
}
|
||||
varHistory[sourceInfo.RepoURL] = append(varHistory[sourceInfo.RepoURL], history{
|
||||
id: depInfo.ID,
|
||||
date: depInfo.DeployedAt.String(),
|
||||
revision: rev,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
rev := depInfo.Source.TargetRevision
|
||||
if len(depInfo.Revision) >= MAX_ALLOWED_REVISIONS {
|
||||
rev = fmt.Sprintf("%s (%s)", rev, depInfo.Revision[0:MAX_ALLOWED_REVISIONS])
|
||||
}
|
||||
if _, ok := varHistory[depInfo.Source.RepoURL]; !ok {
|
||||
varHistoryKeys = append(varHistoryKeys, depInfo.Source.RepoURL)
|
||||
}
|
||||
varHistory[depInfo.Source.RepoURL] = append(varHistory[depInfo.Source.RepoURL], history{
|
||||
id: depInfo.ID,
|
||||
date: depInfo.DeployedAt.String(),
|
||||
revision: rev,
|
||||
})
|
||||
}
|
||||
}
|
||||
for i, key := range varHistoryKeys {
|
||||
_, _ = fmt.Fprintf(w, "SOURCE\t%s\n", key)
|
||||
_, _ = fmt.Fprintf(w, "ID\tDATE\tREVISION\n")
|
||||
for _, history := range varHistory[key] {
|
||||
_, _ = fmt.Fprintf(w, "%d\t%s\t%s\n", history.id, history.date, history.revision)
|
||||
}
|
||||
// Add a newline if it's not the last iteration
|
||||
if i < len(varHistoryKeys)-1 {
|
||||
_, _ = fmt.Fprintf(w, "\n")
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, "%d\t%s\t%s\n", depInfo.ID, depInfo.DeployedAt, rev)
|
||||
}
|
||||
_ = w.Flush()
|
||||
}
|
||||
@@ -2408,7 +2595,8 @@ func printApplicationHistoryTable(revHistory []argoappv1.RevisionHistory) {
|
||||
// NewApplicationHistoryCommand returns a new instance of an `argocd app history` command
|
||||
func NewApplicationHistoryCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
output string
|
||||
output string
|
||||
appNamespace string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "history APPNAME",
|
||||
@@ -2422,7 +2610,7 @@ func NewApplicationHistoryCommand(clientOpts *argocdclient.ClientOptions) *cobra
|
||||
}
|
||||
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], "")
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
|
||||
app, err := appIf.Get(ctx, &application.ApplicationQuery{
|
||||
Name: &appName,
|
||||
AppNamespace: &appNs,
|
||||
@@ -2436,6 +2624,7 @@ func NewApplicationHistoryCommand(clientOpts *argocdclient.ClientOptions) *cobra
|
||||
}
|
||||
},
|
||||
}
|
||||
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Only show application deployment history in namespace")
|
||||
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: wide|id")
|
||||
return command
|
||||
}
|
||||
@@ -2460,9 +2649,10 @@ func findRevisionHistory(application *argoappv1.Application, historyId int64) (*
|
||||
// NewApplicationRollbackCommand returns a new instance of an `argocd app rollback` command
|
||||
func NewApplicationRollbackCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
prune bool
|
||||
timeout uint
|
||||
output string
|
||||
prune bool
|
||||
timeout uint
|
||||
output string
|
||||
appNamespace string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "rollback APPNAME [ID]",
|
||||
@@ -2473,7 +2663,7 @@ func NewApplicationRollbackCommand(clientOpts *argocdclient.ClientOptions) *cobr
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], "")
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
|
||||
var err error
|
||||
depID := -1
|
||||
if len(args) > 1 {
|
||||
@@ -2509,6 +2699,7 @@ func NewApplicationRollbackCommand(clientOpts *argocdclient.ClientOptions) *cobr
|
||||
command.Flags().BoolVar(&prune, "prune", false, "Allow deleting unexpected resources")
|
||||
command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds")
|
||||
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|tree|tree=detailed")
|
||||
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Rollback application in namespace")
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -2521,7 +2712,11 @@ func printOperationResult(opState *argoappv1.OperationState) {
|
||||
}
|
||||
if opState.SyncResult != nil {
|
||||
fmt.Printf(printOpFmtStr, "Operation:", "Sync")
|
||||
fmt.Printf(printOpFmtStr, "Sync Revision:", opState.SyncResult.Revision)
|
||||
if opState.SyncResult.Sources != nil && opState.SyncResult.Revisions != nil {
|
||||
fmt.Printf(printOpFmtStr, "Sync Revision:", strings.Join(opState.SyncResult.Revisions, ", "))
|
||||
} else {
|
||||
fmt.Printf(printOpFmtStr, "Sync Revision:", opState.SyncResult.Revision)
|
||||
}
|
||||
}
|
||||
fmt.Printf(printOpFmtStr, "Phase:", opState.Phase)
|
||||
fmt.Printf(printOpFmtStr, "Start:", opState.StartedAt)
|
||||
@@ -2543,12 +2738,24 @@ func NewApplicationManifestsCommand(clientOpts *argocdclient.ClientOptions) *cob
|
||||
var (
|
||||
source string
|
||||
revision string
|
||||
revisions []string
|
||||
sourceIndexes []int64
|
||||
local string
|
||||
localRepoRoot string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "manifests APPNAME",
|
||||
Short: "Print manifests of an application",
|
||||
Example: templates.Examples(`
|
||||
# Get manifests for an application
|
||||
argocd app manifests my-app
|
||||
|
||||
# Get manifests for an application at a specific revision
|
||||
argocd app manifests my-app --revision 0.0.1
|
||||
|
||||
# Get manifests for a multi-source application at specific revisions for specific sources
|
||||
argocd app manifests my-app --revisions 0.0.1 --source-indexes 1 --revisions 0.0.2 --source-indexes 2
|
||||
`),
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
ctx := c.Context()
|
||||
|
||||
@@ -2556,10 +2763,16 @@ func NewApplicationManifestsCommand(clientOpts *argocdclient.ClientOptions) *cob
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(revisions) != len(sourceIndexes) {
|
||||
errors.CheckError(fmt.Errorf("While using revisions and source-indexes, length of values for both flags should be same."))
|
||||
}
|
||||
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], "")
|
||||
clientset := headless.NewClientOrDie(clientOpts, c)
|
||||
conn, appIf := clientset.NewApplicationClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
|
||||
resources, err := appIf.ManagedResources(ctx, &application.ResourcesQuery{
|
||||
ApplicationName: &appName,
|
||||
AppNamespace: &appNs,
|
||||
@@ -2585,6 +2798,30 @@ func NewApplicationManifestsCommand(clientOpts *argocdclient.ClientOptions) *cob
|
||||
|
||||
proj := getProject(c, clientOpts, ctx, app.Spec.Project)
|
||||
unstructureds = getLocalObjects(context.Background(), app, proj.Project, local, localRepoRoot, argoSettings.AppLabelKey, cluster.ServerVersion, cluster.Info.APIVersions, argoSettings.KustomizeOptions, argoSettings.TrackingMethod)
|
||||
} else if len(revisions) > 0 && len(sourceIndexes) > 0 {
|
||||
|
||||
revisionSourceMappings := make(map[int64]string, 0)
|
||||
for i, index := range sourceIndexes {
|
||||
if index <= 0 {
|
||||
errors.CheckError(fmt.Errorf("source-index cannot be less than or equal to 0, Index starts at 1"))
|
||||
}
|
||||
revisionSourceMappings[index] = revisions[i]
|
||||
}
|
||||
|
||||
q := application.ApplicationManifestQuery{
|
||||
Name: &appName,
|
||||
AppNamespace: &appNs,
|
||||
Revision: pointer.String(revision),
|
||||
RevisionSourceMappings: revisionSourceMappings,
|
||||
}
|
||||
res, err := appIf.GetManifests(ctx, &q)
|
||||
errors.CheckError(err)
|
||||
|
||||
for _, mfst := range res.Manifests {
|
||||
obj, err := argoappv1.UnmarshalToUnstructured(mfst)
|
||||
errors.CheckError(err)
|
||||
unstructureds = append(unstructureds, obj)
|
||||
}
|
||||
} else if revision != "" {
|
||||
q := application.ApplicationManifestQuery{
|
||||
Name: &appName,
|
||||
@@ -2622,6 +2859,8 @@ func NewApplicationManifestsCommand(clientOpts *argocdclient.ClientOptions) *cob
|
||||
}
|
||||
command.Flags().StringVar(&source, "source", "git", "Source of manifests. One of: live|git")
|
||||
command.Flags().StringVar(&revision, "revision", "", "Show manifests at a specific revision")
|
||||
command.Flags().StringArrayVar(&revisions, "revisions", []string{}, "Show manifests at specific revisions for the index of sources in source-indexes")
|
||||
command.Flags().Int64SliceVar(&sourceIndexes, "source-indexes", []int64{}, "List of source indexes. Default is empty array. Indexes start at 1.")
|
||||
command.Flags().StringVar(&local, "local", "", "If set, show locally-generated manifests. Value is the absolute path to app manifests within the manifest repo. Example: '/home/username/apps/env/app-1'.")
|
||||
command.Flags().StringVar(&localRepoRoot, "local-repo-root", ".", "Path to the local repository root. Used together with --local allows setting the repository root. Example: '/home/username/apps'.")
|
||||
return command
|
||||
@@ -2654,6 +2893,9 @@ func NewApplicationTerminateOpCommand(clientOpts *argocdclient.ClientOptions) *c
|
||||
}
|
||||
|
||||
func NewApplicationEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
appNamespace string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "edit APPNAME",
|
||||
Short: "Edit application",
|
||||
@@ -2664,7 +2906,8 @@ func NewApplicationEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], "")
|
||||
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
|
||||
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
app, err := appIf.Get(ctx, &application.ApplicationQuery{
|
||||
@@ -2690,7 +2933,11 @@ func NewApplicationEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
}
|
||||
|
||||
var appOpts cmdutil.AppOptions
|
||||
cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts)
|
||||
|
||||
// do not allow overrides for applications with multiple sources
|
||||
if !app.Spec.HasMultipleSources() {
|
||||
cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts, 0)
|
||||
}
|
||||
_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
|
||||
Name: &appName,
|
||||
Spec: &updatedSpec,
|
||||
@@ -2704,12 +2951,16 @@ func NewApplicationEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
})
|
||||
},
|
||||
}
|
||||
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Only edit application in namespace")
|
||||
return command
|
||||
}
|
||||
|
||||
func NewApplicationPatchCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var patch string
|
||||
var patchType string
|
||||
var (
|
||||
patch string
|
||||
patchType string
|
||||
appNamespace string
|
||||
)
|
||||
|
||||
command := cobra.Command{
|
||||
Use: "patch APPNAME",
|
||||
@@ -2726,7 +2977,7 @@ func NewApplicationPatchCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], "")
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
|
||||
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
|
||||
@@ -2744,8 +2995,137 @@ func NewApplicationPatchCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
|
||||
fmt.Println(string(yamlBytes))
|
||||
},
|
||||
}
|
||||
|
||||
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Only patch application in namespace")
|
||||
command.Flags().StringVar(&patch, "patch", "", "Patch body")
|
||||
command.Flags().StringVar(&patchType, "type", "json", "The type of patch being provided; one of [json merge]")
|
||||
return &command
|
||||
}
|
||||
|
||||
// NewApplicationAddSourceCommand returns a new instance of an `argocd app add-source` command
|
||||
func NewApplicationAddSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
appOpts cmdutil.AppOptions
|
||||
appNamespace string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "add-source APPNAME",
|
||||
Short: "Adds a source to the list of sources in the application",
|
||||
Example: ` # Append a source to the list of sources in the application
|
||||
argocd app add-source guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook`,
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
ctx := c.Context()
|
||||
if len(args) != 1 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
argocdClient := headless.NewClientOrDie(clientOpts, c)
|
||||
conn, appIf := argocdClient.NewApplicationClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
|
||||
|
||||
app, err := appIf.Get(ctx, &application.ApplicationQuery{
|
||||
Name: &appName,
|
||||
Refresh: getRefreshType(false, false),
|
||||
AppNamespace: &appNs,
|
||||
})
|
||||
|
||||
errors.CheckError(err)
|
||||
|
||||
if c.Flags() == nil {
|
||||
errors.CheckError(fmt.Errorf("ApplicationSource needs atleast repoUrl, path or chart or ref field. No source to add."))
|
||||
}
|
||||
|
||||
if len(app.Spec.Sources) > 0 {
|
||||
appSource, _ := cmdutil.ConstructSource(&argoappv1.ApplicationSource{}, appOpts, c.Flags())
|
||||
|
||||
// sourceIndex is the index at which new source will be appended to spec.Sources
|
||||
sourceIndex := len(app.Spec.GetSources())
|
||||
app.Spec.Sources = append(app.Spec.Sources, *appSource)
|
||||
|
||||
setParameterOverrides(app, appOpts.Parameters, sourceIndex)
|
||||
|
||||
_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
|
||||
Name: &app.Name,
|
||||
Spec: &app.Spec,
|
||||
Validate: &appOpts.Validate,
|
||||
AppNamespace: &appNs,
|
||||
})
|
||||
errors.CheckError(err)
|
||||
|
||||
fmt.Printf("Application '%s' updated successfully\n", app.ObjectMeta.Name)
|
||||
} else {
|
||||
errors.CheckError(fmt.Errorf("Cannot add source: application %s does not have spec.sources defined", appName))
|
||||
}
|
||||
},
|
||||
}
|
||||
cmdutil.AddAppFlags(command, &appOpts)
|
||||
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Namespace of the target application where the source will be appended")
|
||||
return command
|
||||
}
|
||||
|
||||
// NewApplicationRemoveSourceCommand returns a new instance of an `argocd app remove-source` command
|
||||
func NewApplicationRemoveSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
sourceIndex int
|
||||
appNamespace string
|
||||
)
|
||||
command := &cobra.Command{
|
||||
Use: "remove-source APPNAME",
|
||||
Short: "Remove a source from multiple sources application. Index starts with 1. Default value is -1.",
|
||||
Example: ` # Remove the source at index 1 from application's sources. Index starts at 1.
|
||||
argocd app remove-source myapplication --source-index 1`,
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
ctx := c.Context()
|
||||
|
||||
if len(args) != 1 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if sourceIndex <= 0 {
|
||||
errors.CheckError(fmt.Errorf("Index value of source must be greater than 0"))
|
||||
}
|
||||
|
||||
argocdClient := headless.NewClientOrDie(clientOpts, c)
|
||||
conn, appIf := argocdClient.NewApplicationClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
|
||||
appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
|
||||
|
||||
app, err := appIf.Get(ctx, &application.ApplicationQuery{
|
||||
Name: &appName,
|
||||
Refresh: getRefreshType(false, false),
|
||||
AppNamespace: &appNs,
|
||||
})
|
||||
errors.CheckError(err)
|
||||
|
||||
if !app.Spec.HasMultipleSources() {
|
||||
errors.CheckError(fmt.Errorf("Application does not have multiple sources configured"))
|
||||
}
|
||||
|
||||
if len(app.Spec.GetSources()) == 1 {
|
||||
errors.CheckError(fmt.Errorf("Cannot remove the only source remaining in the app"))
|
||||
}
|
||||
|
||||
if len(app.Spec.GetSources()) < sourceIndex {
|
||||
errors.CheckError(fmt.Errorf("Application does not have source at %d\n", sourceIndex))
|
||||
}
|
||||
|
||||
app.Spec.Sources = append(app.Spec.Sources[:sourceIndex-1], app.Spec.Sources[sourceIndex:]...)
|
||||
|
||||
_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
|
||||
Name: &app.Name,
|
||||
Spec: &app.Spec,
|
||||
AppNamespace: &appNs,
|
||||
})
|
||||
errors.CheckError(err)
|
||||
|
||||
fmt.Printf("Application '%s' updated successfully\n", app.ObjectMeta.Name)
|
||||
},
|
||||
}
|
||||
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Namespace of the target application where the source will be appended")
|
||||
command.Flags().IntVar(&sourceIndex, "source-index", -1, "Index of the source from the list of sources of the app. Index starts from 1.")
|
||||
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, index int) int {
|
||||
visited := 0
|
||||
if flags == nil {
|
||||
return visited
|
||||
}
|
||||
source := spec.GetSourcePtr(index)
|
||||
if source == nil {
|
||||
source = &argoappv1.ApplicationSource{}
|
||||
}
|
||||
source, visited = ConstructSource(source, *appOpts, flags)
|
||||
if spec.HasMultipleSources() {
|
||||
if index == 0 {
|
||||
spec.Sources[index] = *source
|
||||
} else if index > 0 {
|
||||
spec.Sources[index-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.GetSourcePtr(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) SetFlagWithSourceIndex(key, value string, index int) error {
|
||||
err := f.command.Flags().Set(key, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = SetAppSpecOptions(f.command.Flags(), f.spec, f.options, index)
|
||||
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()
|
||||
index := 0
|
||||
index1 := 1
|
||||
index2 := 2
|
||||
t.Run("SyncPolicy", func(t *testing.T) {
|
||||
assert.NoError(t, f.SetFlagWithSourceIndex("sync-policy", "automated", index1))
|
||||
assert.NotNil(t, f.spec.SyncPolicy.Automated)
|
||||
|
||||
f.spec.SyncPolicy = nil
|
||||
assert.NoError(t, f.SetFlagWithSourceIndex("sync-policy", "automatic", index1))
|
||||
assert.NotNil(t, f.spec.SyncPolicy.Automated)
|
||||
})
|
||||
t.Run("Helm - Index 0", func(t *testing.T) {
|
||||
assert.NoError(t, f.SetFlagWithSourceIndex("helm-version", "v2", index))
|
||||
assert.Equal(t, len(f.spec.GetSources()), 2)
|
||||
assert.Equal(t, f.spec.GetSources()[index].Helm.Version, "v2")
|
||||
})
|
||||
t.Run("Kustomize", func(t *testing.T) {
|
||||
assert.NoError(t, f.SetFlagWithSourceIndex("kustomize-replica", "my-deployment=2", index1))
|
||||
assert.Equal(t, f.spec.Sources[index1-1].Kustomize.Replicas, v1alpha1.KustomizeReplicas{{Name: "my-deployment", Count: intstr.FromInt(2)}})
|
||||
assert.NoError(t, f.SetFlagWithSourceIndex("kustomize-replica", "my-deployment=4", index2))
|
||||
assert.Equal(t, f.spec.Sources[index2-1].Kustomize.Replicas, v1alpha1.KustomizeReplicas{{Name: "my-deployment", Count: intstr.FromInt(4)}})
|
||||
})
|
||||
t.Run("Helm", func(t *testing.T) {
|
||||
assert.NoError(t, f.SetFlagWithSourceIndex("helm-version", "v2", index1))
|
||||
assert.NoError(t, f.SetFlagWithSourceIndex("helm-version", "v3", index2))
|
||||
assert.Equal(t, len(f.spec.GetSources()), 2)
|
||||
assert.Equal(t, f.spec.GetSources()[index1-1].Helm.Version, "v2")
|
||||
assert.Equal(t, f.spec.GetSources()[index2-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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -48,7 +48,6 @@ 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"
|
||||
@@ -511,13 +510,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,
|
||||
@@ -797,7 +796,13 @@ func (ctrl *ApplicationController) Run(ctx context.Context, statusProcessors int
|
||||
if err != nil {
|
||||
log.Warnf("Cannot init sharding. Error while querying clusters list from database: %v", err)
|
||||
} else {
|
||||
ctrl.clusterSharding.Init(clusters)
|
||||
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())
|
||||
@@ -1051,7 +1056,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 {
|
||||
@@ -2107,6 +2112,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) {
|
||||
@@ -2137,6 +2146,7 @@ func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.Shar
|
||||
|
||||
ctrl.requestAppRefresh(newApp.QualifiedName(), compareWith, delay)
|
||||
ctrl.appOperationQueue.AddRateLimited(key)
|
||||
ctrl.clusterSharding.UpdateApp(newApp)
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
if !ctrl.canProcessApp(obj) {
|
||||
@@ -2149,6 +2159,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)
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -2224,4 +2238,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
|
||||
|
||||
@@ -53,14 +53,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 {
|
||||
@@ -106,6 +107,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{
|
||||
|
||||
9
controller/cache/cache.go
vendored
@@ -372,9 +372,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")
|
||||
}
|
||||
@@ -432,6 +437,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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,16 @@ import (
|
||||
)
|
||||
|
||||
type ClusterShardingCache interface {
|
||||
Init(clusters *v1alpha1.ClusterList)
|
||||
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 {
|
||||
@@ -22,6 +26,7 @@ type ClusterSharding struct {
|
||||
Replicas int
|
||||
Shards map[string]int
|
||||
Clusters map[string]*v1alpha1.Cluster
|
||||
Apps map[string]*v1alpha1.Application
|
||||
lock sync.RWMutex
|
||||
getClusterShard DistributionFunction
|
||||
}
|
||||
@@ -33,11 +38,12 @@ func NewClusterSharding(_ db.ArgoDB, shard, replicas int, shardingAlgorithm stri
|
||||
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(), shardingAlgorithm, replicas)
|
||||
distributionFunction = GetDistributionFunction(clusterSharding.getClusterAccessor(), clusterSharding.getAppAccessor(), shardingAlgorithm, replicas)
|
||||
} else {
|
||||
log.Info("Processing all cluster shards")
|
||||
}
|
||||
@@ -62,7 +68,7 @@ func (s *ClusterSharding) IsManagedCluster(c *v1alpha1.Cluster) bool {
|
||||
return clusterShard == s.Shard
|
||||
}
|
||||
|
||||
func (sharding *ClusterSharding) Init(clusters *v1alpha1.ClusterList) {
|
||||
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))
|
||||
@@ -71,6 +77,13 @@ func (sharding *ClusterSharding) Init(clusters *v1alpha1.ClusterList) {
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -173,7 +186,8 @@ func hasShardingUpdates(old, new *v1alpha1.Cluster) bool {
|
||||
return old.Shard == nil || new.Shard == nil || int64(*old.Shard) != int64(*new.Shard)
|
||||
}
|
||||
|
||||
func (d *ClusterSharding) GetClusterAccessor() clusterAccessor {
|
||||
// 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))
|
||||
@@ -183,3 +197,68 @@ func (d *ClusterSharding) GetClusterAccessor() clusterAccessor {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -139,6 +139,12 @@ func TestClusterSharding_Delete(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
&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")
|
||||
@@ -164,6 +170,12 @@ func TestClusterSharding_Update(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1alpha1.ApplicationList{
|
||||
Items: []v1alpha1.Application{
|
||||
createApp("app2", "https://127.0.0.1:6443"),
|
||||
createApp("app1", "https://kubernetes.default.svc"),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
distributionBefore := sharding.GetDistribution()
|
||||
@@ -207,6 +219,12 @@ func TestClusterSharding_UpdateServerName(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1alpha1.ApplicationList{
|
||||
Items: []v1alpha1.Application{
|
||||
createApp("app2", "https://127.0.0.1:6443"),
|
||||
createApp("app1", "https://kubernetes.default.svc"),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
distributionBefore := sharding.GetDistribution()
|
||||
@@ -251,6 +269,12 @@ func TestClusterSharding_IsManagedCluster(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
&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{
|
||||
@@ -278,6 +302,12 @@ func TestClusterSharding_IsManagedCluster(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
&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{
|
||||
@@ -327,6 +357,12 @@ func TestClusterSharding_ClusterShardOfResourceShouldNotBeChanged(t *testing.T)
|
||||
*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))
|
||||
|
||||
@@ -43,6 +43,7 @@ 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.
|
||||
@@ -75,7 +76,7 @@ func GetClusterFilter(db db.ArgoDB, distributionFunction DistributionFunction, r
|
||||
|
||||
// GetDistributionFunction returns which DistributionFunction should be used based on the passed algorithm and
|
||||
// the current datas.
|
||||
func GetDistributionFunction(clusters clusterAccessor, shardingAlgorithm string, replicasCount int) DistributionFunction {
|
||||
func GetDistributionFunction(clusters clusterAccessor, apps appAccessor, shardingAlgorithm string, replicasCount int) DistributionFunction {
|
||||
log.Debugf("Using filter function: %s", shardingAlgorithm)
|
||||
distributionFunction := LegacyDistributionFunction(replicasCount)
|
||||
switch shardingAlgorithm {
|
||||
@@ -374,13 +375,13 @@ func GetClusterSharding(kubeClient kubernetes.Interface, settingsMgr *settings.S
|
||||
|
||||
// if app controller deployment is not found when dynamic cluster distribution is enabled error out
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("(dymanic cluster distribution) failed to get app controller deployment: %v", err)
|
||||
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("(dymanic cluster distribution) failed to get app controller deployment replica count")
|
||||
return nil, fmt.Errorf("(dynamic cluster distribution) failed to get app controller deployment replica count")
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
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) {
|
||||
@@ -101,13 +102,14 @@ func TestGetClusterFilterLegacy(t *testing.T) {
|
||||
|
||||
func TestGetClusterFilterUnknown(t *testing.T) {
|
||||
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")
|
||||
replicasCount := 2
|
||||
db.On("GetApplicationControllerReplicas").Return(replicasCount)
|
||||
distributionFunction := GetDistributionFunction(clusterAccessor, "unknown", 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))
|
||||
@@ -119,9 +121,10 @@ 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,...)
|
||||
t.Setenv(common.EnvControllerReplicas, "5")
|
||||
clusterAccessor, db, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters()
|
||||
appAccessor, _, _, _, _, _ := createTestApps()
|
||||
replicasCount := 5
|
||||
db.On("GetApplicationControllerReplicas").Return(replicasCount)
|
||||
filter := GetDistributionFunction(clusterAccessor, common.DefaultShardingAlgorithm, 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))
|
||||
@@ -131,13 +134,13 @@ func TestLegacyGetClusterFilterWithFixedShard(t *testing.T) {
|
||||
var fixedShard int64 = 4
|
||||
cluster5 := &v1alpha1.Cluster{ID: "5", Shard: &fixedShard}
|
||||
clusterAccessor = getClusterAccessor([]v1alpha1.Cluster{cluster1, cluster2, cluster2, cluster4, *cluster5})
|
||||
filter = GetDistributionFunction(clusterAccessor, common.DefaultShardingAlgorithm, replicasCount)
|
||||
filter = GetDistributionFunction(clusterAccessor, appAccessor, common.DefaultShardingAlgorithm, replicasCount)
|
||||
assert.Equal(t, int(fixedShard), filter(cluster5))
|
||||
|
||||
fixedShard = 1
|
||||
cluster5.Shard = &fixedShard
|
||||
clusterAccessor = getClusterAccessor([]v1alpha1.Cluster{cluster1, cluster2, cluster2, cluster4, *cluster5})
|
||||
filter = GetDistributionFunction(clusterAccessor, common.DefaultShardingAlgorithm, replicasCount)
|
||||
filter = GetDistributionFunction(clusterAccessor, appAccessor, common.DefaultShardingAlgorithm, replicasCount)
|
||||
assert.Equal(t, int(fixedShard), filter(&v1alpha1.Cluster{ID: "4", Shard: &fixedShard}))
|
||||
}
|
||||
|
||||
@@ -145,10 +148,11 @@ 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,...)
|
||||
t.Setenv(common.EnvControllerReplicas, "4")
|
||||
clusterAccessor, db, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters()
|
||||
appAccessor, _, _, _, _, _ := createTestApps()
|
||||
replicasCount := 4
|
||||
db.On("GetApplicationControllerReplicas").Return(replicasCount)
|
||||
|
||||
filter := GetDistributionFunction(clusterAccessor, common.RoundRobinShardingAlgorithm, 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)
|
||||
@@ -161,14 +165,14 @@ func TestRoundRobinGetClusterFilterWithFixedShard(t *testing.T) {
|
||||
cluster5 := v1alpha1.Cluster{Name: "cluster5", ID: "5", Shard: &fixedShard}
|
||||
clusters := []v1alpha1.Cluster{cluster1, cluster2, cluster3, cluster4, cluster5}
|
||||
clusterAccessor = getClusterAccessor(clusters)
|
||||
filter = GetDistributionFunction(clusterAccessor, common.RoundRobinShardingAlgorithm, replicasCount)
|
||||
filter = GetDistributionFunction(clusterAccessor, appAccessor, common.RoundRobinShardingAlgorithm, replicasCount)
|
||||
assert.Equal(t, int(fixedShard), filter(&cluster5))
|
||||
|
||||
fixedShard = 1
|
||||
cluster5 = v1alpha1.Cluster{Name: "cluster5", ID: "5", Shard: &fixedShard}
|
||||
clusters = []v1alpha1.Cluster{cluster1, cluster2, cluster3, cluster4, cluster5}
|
||||
clusterAccessor = getClusterAccessor(clusters)
|
||||
filter = GetDistributionFunction(clusterAccessor, common.RoundRobinShardingAlgorithm, replicasCount)
|
||||
filter = GetDistributionFunction(clusterAccessor, appAccessor, common.RoundRobinShardingAlgorithm, replicasCount)
|
||||
assert.Equal(t, int(fixedShard), filter(&v1alpha1.Cluster{Name: "cluster4", ID: "4", Shard: &fixedShard}))
|
||||
}
|
||||
|
||||
@@ -842,7 +846,7 @@ func TestGetClusterSharding(t *testing.T) {
|
||||
useDynamicSharding: true,
|
||||
expectedShard: 0,
|
||||
expectedReplicas: 1,
|
||||
expectedErr: fmt.Errorf("(dymanic cluster distribution) failed to get app controller deployment: deployments.apps \"missing-deployment\" not found"),
|
||||
expectedErr: fmt.Errorf("(dynamic cluster distribution) failed to get app controller deployment: deployments.apps \"missing-deployment\" not found"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -870,3 +874,81 @@ func TestGetClusterSharding(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ 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"
|
||||
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
|
||||
@@ -194,6 +195,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{
|
||||
@@ -880,7 +913,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 +935,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 +944,7 @@ func (m *appStateManager) persistRevisionHistory(app *v1alpha1.Application, revi
|
||||
DeployStartedAt: &startedAt,
|
||||
ID: nextID,
|
||||
Source: source,
|
||||
InitiatedBy: initiatedBy,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -254,6 +254,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
|
||||
@@ -386,3 +455,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).
|
||||
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):
|
||||
|
||||
@@ -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,6 +32,12 @@ 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.
|
||||
|
||||
## 2. Download Argo CD CLI
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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)`
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -12,7 +12,8 @@ An additional `normalize` function makes any string parameter usable as a valid
|
||||
with hyphens and truncating at 253 characters. This is useful when making parameters safe for things like Application
|
||||
names.
|
||||
|
||||
Another function has `slugify` function has been added which, by default, sanitizes and smart truncate (means doesn't cut a word into 2). This function accepts a couple of arguments:
|
||||
Another `slugify` function has been added which, by default, sanitizes and smart truncates (it doesn't cut a word into 2). This function accepts a couple of arguments:
|
||||
|
||||
- The first argument (if provided) is an integer specifying the maximum length of the slug.
|
||||
- The second argument (if provided) is a boolean indicating whether smart truncation is enabled.
|
||||
- The last argument (if provided) is the input name that needs to be slugified.
|
||||
@@ -206,6 +207,8 @@ ApplicationSet controller provides:
|
||||
1. contains no more than 253 characters
|
||||
2. contains only lowercase alphanumeric characters, '-' or '.'
|
||||
3. starts and ends with an alphanumeric character
|
||||
|
||||
- `slugify`: sanitizes like `normalize` and smart truncates (it doesn't cut a word into 2) like described in the [introduction](#introduction) section.
|
||||
- `toYaml` / `fromYaml` / `fromYamlArray` helm like functions
|
||||
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ spec:
|
||||
spec:
|
||||
project: "default"
|
||||
source:
|
||||
revision: HEAD
|
||||
targetRevision: HEAD
|
||||
repoURL: https://github.com/argoproj/argo-cd.git
|
||||
# New path value is generated here:
|
||||
path: 'applicationset/examples/template-override/{{cluster}}-override'
|
||||
@@ -99,7 +99,7 @@ spec:
|
||||
source:
|
||||
repoURL: https://github.com/argoproj/argo-cd.git
|
||||
targetRevision: HEAD
|
||||
# This 'default' value is not used: it is is replaced by the generator's template path, above
|
||||
# This 'default' value is not used: it is replaced by the generator's template path, above
|
||||
path: applicationset/examples/template-override/default
|
||||
destination:
|
||||
server: '{{url}}'
|
||||
|
||||
@@ -235,14 +235,6 @@ data:
|
||||
# can be either empty, "normal" or "strict". By default, it is empty i.e. disabled.
|
||||
resource.respectRBAC: "normal"
|
||||
|
||||
# Configuration to add a config management plugin.
|
||||
configManagementPlugins: |
|
||||
- name: kasane
|
||||
init:
|
||||
command: [kasane, update]
|
||||
generate:
|
||||
command: [kasane, show]
|
||||
|
||||
# A set of settings that allow enabling or disabling the config management tool.
|
||||
# If unset, each defaults to "true".
|
||||
kustomize.enabled: true
|
||||
@@ -308,8 +300,10 @@ data:
|
||||
# have either a permanent banner or a regular closeable banner, and NOT both. eg. A user can't dismiss a
|
||||
# notification message (closeable) banner, to then immediately see a permanent banner.
|
||||
# ui.bannerpermanent: "true"
|
||||
# An option to specify the position of the banner, either the top or bottom of the page. The default is at the top.
|
||||
# Uncomment to make the banner appear at the bottom of the page. Any value other than "bottom" will make the banner appear at the top.
|
||||
# An option to specify the position of the banner, either the top or bottom of the page, or both. The valid values
|
||||
# are: "top", "bottom" and "both". The default (if the option is not provided), is "top". If "both" is specified, then
|
||||
# the content appears both at the top and the bottom of the page. Uncomment the following line to make the banner appear
|
||||
# at the bottom of the page. Change the value as needed.
|
||||
# ui.bannerposition: "bottom"
|
||||
|
||||
# Application reconciliation timeout is the max amount of time required to discover if a new manifests version got
|
||||
@@ -326,6 +320,10 @@ data:
|
||||
# cluster.inClusterEnabled indicates whether to allow in-cluster server address. This is enabled by default.
|
||||
cluster.inClusterEnabled: "true"
|
||||
|
||||
# The maximum number of pod logs to render in UI. If the application has more than this number of pods, the logs will not be rendered.
|
||||
# This is to prevent the UI from becoming unresponsive when rendering a large number of logs. Default is 10.
|
||||
server.maxPodLogsToRender: 10
|
||||
|
||||
# Application pod logs RBAC enforcement enables control over who can and who can't view application pod logs.
|
||||
# When you enable the switch, pod logs will be visible only to admin role by default. Other roles/users will not be able to view them via cli and UI.
|
||||
# When you enable the switch, viewing pod logs for other roles/users will require explicit RBAC allow policies (allow get on logs subresource).
|
||||
|
||||
@@ -25,7 +25,7 @@ A few use-cases that justify running Argo CD Core are:
|
||||
|
||||
- As a cluster admin, I want to rely on Kubernetes RBAC only.
|
||||
- As a devops engineer, I don't want to learn a new API or depend on
|
||||
another CLI to automate my deployments. I want instead rely in
|
||||
another CLI to automate my deployments. I want to rely on the
|
||||
Kubernetes API only.
|
||||
- As a cluster admin, I don't want to provide Argo CD UI or Argo CD
|
||||
CLI to developers.
|
||||
|
||||
@@ -549,6 +549,7 @@ bearerToken: string
|
||||
awsAuthConfig:
|
||||
clusterName: string
|
||||
roleARN: string
|
||||
profile: string
|
||||
# Configure external command to supply client credentials
|
||||
# See https://godoc.org/k8s.io/client-go/tools/clientcmd/api#ExecConfig
|
||||
execProviderConfig:
|
||||
@@ -669,9 +670,9 @@ extended to allow assumption of multiple roles, either as an explicit array of r
|
||||
"Statement" : {
|
||||
"Effect" : "Allow",
|
||||
"Action" : "sts:AssumeRole",
|
||||
"Principal" : {
|
||||
"AWS" : "<arn:aws:iam::<AWS_ACCOUNT_ID>:role/<IAM_ROLE_NAME>"
|
||||
}
|
||||
"Resource" : [
|
||||
"<arn:aws:iam::<AWS_ACCOUNT_ID>:role/<IAM_ROLE_NAME>"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -731,6 +732,140 @@ data:
|
||||
"rolearn": "<arn:aws:iam::<AWS_ACCOUNT_ID>:role/<IAM_ROLE_NAME>"
|
||||
"username": "<some-username>"
|
||||
```
|
||||
|
||||
#### Alternative EKS Authentication Methods
|
||||
In some scenarios it may not be possible to use IRSA, such as when the Argo CD cluster is running on a different cloud
|
||||
provider's platform. In this case, there are two options:
|
||||
1. Use `execProviderConfig` to call the AWS authentication mechanism which enables the injection of environment variables to supply credentials
|
||||
2. Leverage the new AWS profile option available in Argo CD release 2.10
|
||||
|
||||
Both of these options will require the steps involving IAM and the `aws-auth` config map (defined above) to provide the
|
||||
principal with access to the cluster.
|
||||
|
||||
##### Using execProviderConfig with Environment Variables
|
||||
```yaml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: mycluster-secret
|
||||
labels:
|
||||
argocd.argoproj.io/secret-type: cluster
|
||||
type: Opaque
|
||||
stringData:
|
||||
name: mycluster
|
||||
server: https://mycluster.example.com
|
||||
namespaces: "my,managed,namespaces"
|
||||
clusterResources: "true"
|
||||
config: |
|
||||
{
|
||||
"execProviderConfig": {
|
||||
"command": "argocd-k8s-auth",
|
||||
"args": ["aws", "--cluster-name", "my-eks-cluster"],
|
||||
"apiVersion": "client.authentication.k8s.io/v1beta1",
|
||||
"env": {
|
||||
"AWS_REGION": "xx-east-1",
|
||||
"AWS_ACCESS_KEY_ID": "{{ .aws_key_id }}",
|
||||
"AWS_SECRET_ACCESS_KEY": "{{ .aws_key_secret }}",
|
||||
"AWS_SESSION_TOKEN": "{{ .aws_token }}"
|
||||
}
|
||||
},
|
||||
"tlsClientConfig": {
|
||||
"insecure": false,
|
||||
"caData": "{{ .cluster_cert }}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This example assumes that the role being attached to the credentials that have been supplied, if this is not the case
|
||||
the role can be appended to the `args` section like so:
|
||||
|
||||
```yaml
|
||||
...
|
||||
"args": ["aws", "--cluster-name", "my-eks-cluster", "--roleARN", "arn:aws:iam::<AWS_ACCOUNT_ID>:role/<IAM_ROLE_NAME>"],
|
||||
...
|
||||
```
|
||||
This construct can be used in conjunction with something like the External Secrets Operator to avoid storing the keys in
|
||||
plain text and additionally helps to provide a foundation for key rotation.
|
||||
|
||||
##### Using An AWS Profile For Authentication
|
||||
The option to use profiles, added in release 2.10, provides a method for supplying credentials while still using the
|
||||
standard Argo CD EKS cluster declaration with an additional command flag that points to an AWS credentials file:
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: mycluster-secret
|
||||
labels:
|
||||
argocd.argoproj.io/secret-type: cluster
|
||||
type: Opaque
|
||||
stringData:
|
||||
name: "mycluster.com"
|
||||
server: "https://mycluster.com"
|
||||
config: |
|
||||
{
|
||||
"awsAuthConfig": {
|
||||
"clusterName": "my-eks-cluster-name",
|
||||
"roleARN": "arn:aws:iam::<AWS_ACCOUNT_ID>:role/<IAM_ROLE_NAME>",
|
||||
"profile": "/mount/path/to/my-profile-file"
|
||||
},
|
||||
"tlsClientConfig": {
|
||||
"insecure": false,
|
||||
"caData": "<base64 encoded certificate>"
|
||||
}
|
||||
}
|
||||
```
|
||||
This will instruct ArgoCD to read the file at the provided path and use the credentials defined within to authenticate to
|
||||
AWS. The profile must be mounted in order for this to work. For example, the following values can be defined in a Helm
|
||||
based ArgoCD deployment:
|
||||
|
||||
```yaml
|
||||
controller:
|
||||
extraVolumes:
|
||||
- name: my-profile-volume
|
||||
secret:
|
||||
secretName: my-aws-profile
|
||||
items:
|
||||
- key: my-profile-file
|
||||
path: my-profile-file
|
||||
extraVolumeMounts:
|
||||
- name: my-profile-mount
|
||||
mountPath: /mount/path/to
|
||||
readOnly: true
|
||||
|
||||
server:
|
||||
extraVolumes:
|
||||
- name: my-profile-volume
|
||||
secret:
|
||||
secretName: my-aws-profile
|
||||
items:
|
||||
- key: my-profile-file
|
||||
path: my-profile-file
|
||||
extraVolumeMounts:
|
||||
- name: my-profile-mount
|
||||
mountPath: /mount/path/to
|
||||
readOnly: true
|
||||
```
|
||||
|
||||
Where the secret is defined as follows:
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: my-aws-profile
|
||||
type: Opaque
|
||||
stringData:
|
||||
my-profile-file: |
|
||||
[default]
|
||||
region = <aws_region>
|
||||
aws_access_key_id = <aws_access_key_id>
|
||||
aws_secret_access_key = <aws_secret_access_key>
|
||||
aws_session_token = <aws_session_token>
|
||||
```
|
||||
|
||||
> ⚠️ Secret mounts are updated on an interval, not real time. If rotation is a requirement ensure the token lifetime outlives the mount update interval and the rotation process doesn't immediately invalidate the existing token
|
||||
|
||||
|
||||
### GKE
|
||||
|
||||
GKE cluster secret example using argocd-k8s-auth and [Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity):
|
||||
@@ -788,6 +923,15 @@ In addition to the environment variables above, argocd-k8s-auth accepts two extr
|
||||
|
||||
This is an example of using the [federated workload login flow](https://github.com/Azure/kubelogin#azure-workload-federated-identity-non-interactive). The federated token file needs to be mounted as a secret into argoCD, so it can be used in the flow. The location of the token file needs to be set in the environment variable AZURE_FEDERATED_TOKEN_FILE.
|
||||
|
||||
If your AKS cluster utilizes the [Mutating Admission Webhook](https://azure.github.io/azure-workload-identity/docs/installation/mutating-admission-webhook.html) from the Azure Workload Identity project, follow these steps to enable the `argocd-application-controller` and `argocd-server` pods to use the federated identity:
|
||||
|
||||
1. **Label the Pods**: Add the `azure.workload.identity/use: "true"` label to the `argocd-application-controller` and `argocd-server` pods.
|
||||
|
||||
2. **Create Federated Identity Credential**: Generate an Azure federated identity credential for the `argocd-application-controller` and `argocd-server` service accounts. Refer to the [Federated Identity Credential](https://azure.github.io/azure-workload-identity/docs/topics/federated-identity-credential.html) documentation for detailed instructions.
|
||||
|
||||
3. **Set the AZURE_CLIENT_ID**: Update the `AZURE_CLIENT_ID` in the cluster secret to match the client id of the newly created federated identity credential.
|
||||
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
@@ -806,9 +950,9 @@ stringData:
|
||||
"env": {
|
||||
"AAD_ENVIRONMENT_NAME": "AzurePublicCloud",
|
||||
"AZURE_CLIENT_ID": "fill in client id",
|
||||
"AZURE_TENANT_ID": "fill in tenant id",
|
||||
"AZURE_FEDERATED_TOKEN_FILE": "/opt/path/to/federated_file.json",
|
||||
"AZURE_AUTHORITY_HOST": "https://login.microsoftonline.com/",
|
||||
"AZURE_TENANT_ID": "fill in tenant id", # optional, injected by workload identity mutating admission webhook if enabled
|
||||
"AZURE_FEDERATED_TOKEN_FILE": "/opt/path/to/federated_file.json", # optional, injected by workload identity mutating admission webhook if enabled
|
||||
"AZURE_AUTHORITY_HOST": "https://login.microsoftonline.com/", # optional, injected by workload identity mutating admission webhook if enabled
|
||||
"AAD_LOGIN_METHOD": "workloadidentity"
|
||||
},
|
||||
"args": ["azure"],
|
||||
@@ -1001,7 +1145,7 @@ Example of `kustomization.yaml`:
|
||||
```yaml
|
||||
# additional resources like ingress rules, cluster and repository secrets.
|
||||
resources:
|
||||
- github.com/argoproj/argo-cd//manifests/cluster-install?ref=v1.0.1
|
||||
- github.com/argoproj/argo-cd//manifests/cluster-install?ref=stable
|
||||
- clusters-secrets.yaml
|
||||
- repos-secrets.yaml
|
||||
|
||||
|
||||
@@ -170,25 +170,27 @@ Argo CD repo server maintains one repository clone locally and uses it for appli
|
||||
Argo CD determines if manifest generation might change local files in the local repository clone based on the config management tool and application settings.
|
||||
If the manifest generation has no side effects then requests are processed in parallel without a performance penalty. The following are known cases that might cause slowness and their workarounds:
|
||||
|
||||
* **Multiple Helm based applications pointing to the same directory in one Git repository:** ensure that your Helm chart doesn't have conditional
|
||||
[dependencies](https://helm.sh/docs/chart_best_practices/dependencies/#conditions-and-tags) and create `.argocd-allow-concurrency` file in the chart directory.
|
||||
* **Multiple Helm based applications pointing to the same directory in one Git repository:** for historical reasons Argo CD generates Helm manifests sequentially. To enable parallel generation set `ARGOCD_HELM_ALLOW_CONCURRENCY=true` to `argocd-repo-server` deployment or create `.argocd-allow-concurrency` file.
|
||||
Future versions of Argo CD will enable this by default.
|
||||
|
||||
* **Multiple Custom plugin based applications:** avoid creating temporal files during manifest generation and create `.argocd-allow-concurrency` file in the app directory, or use the sidecar plugin option, which processes each application using a temporary copy of the repository.
|
||||
|
||||
* **Multiple Kustomize applications in same repository with [parameter overrides](../user-guide/parameters.md):** sorry, no workaround for now.
|
||||
|
||||
|
||||
### Webhook and Manifest Paths Annotation
|
||||
### Manifest Paths Annotation
|
||||
|
||||
Argo CD aggressively caches generated manifests and uses the repository commit SHA as a cache key. A new commit to the Git repository invalidates the cache for all applications configured in the repository.
|
||||
This can negatively affect repositories with multiple applications. You can use [webhooks](https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/webhook.md) and the `argocd.argoproj.io/manifest-generate-paths` Application CRD annotation to solve this problem and improve performance.
|
||||
|
||||
The `argocd.argoproj.io/manifest-generate-paths` annotation contains a semicolon-separated list of paths within the Git repository that are used during manifest generation. The webhook compares paths specified in the annotation with the changed files specified in the webhook payload. If no modified files match the paths specified in `argocd.argoproj.io/manifest-generate-paths`, then the webhook will not trigger application reconciliation and the existing cache will be considered valid for the new commit.
|
||||
The `argocd.argoproj.io/manifest-generate-paths` annotation contains a semicolon-separated list of paths within the Git repository that are used during manifest generation. It will use the paths specified in the annotation to compare the last cached revision to the latest commit. If no modified files match the paths specified in `argocd.argoproj.io/manifest-generate-paths`, then it will not trigger application reconciliation and the existing cache will be considered valid for the new commit.
|
||||
|
||||
Installations that use a different repository for each application are **not** subject to this behavior and will likely get no benefit from using these annotations.
|
||||
|
||||
For webhooks, the comparison is done using the files specified in the webhook event payload instead.
|
||||
|
||||
!!! note
|
||||
Application manifest paths annotation support depends on the git provider used for the Application. It is currently only supported for GitHub, GitLab, and Gogs based repos.
|
||||
Application manifest paths annotation support for webhooks depends on the git provider used for the Application. It is currently only supported for GitHub, GitLab, and Gogs based repos.
|
||||
|
||||
* **Relative path** The annotation might contain a relative path. In this case the path is considered relative to the path specified in the application source:
|
||||
|
||||
@@ -267,13 +269,13 @@ The final rate limiter uses a combination of both and calculates the final backo
|
||||
|
||||
### Global rate limits
|
||||
|
||||
This is enabled by default, it is a simple bucket based rate limiter that limits the number of items that can be queued per second.
|
||||
This is disabled by default, it is a simple bucket based rate limiter that limits the number of items that can be queued per second.
|
||||
This is useful to prevent a large number of apps from being queued at the same time.
|
||||
|
||||
To configure the bucket limiter you can set the following environment variables:
|
||||
|
||||
* `WORKQUEUE_BUCKET_SIZE` - The number of items that can be queued in a single burst. Defaults to 500.
|
||||
* `WORKQUEUE_BUCKET_QPS` - The number of items that can be queued per second. Defaults to 50.
|
||||
* `WORKQUEUE_BUCKET_QPS` - The number of items that can be queued per second. Defaults to MaxFloat64, which disables the limiter.
|
||||
|
||||
### Per item rate limits
|
||||
|
||||
|
||||
@@ -166,6 +166,43 @@ The argocd-server Service needs to be annotated with `projectcontour.io/upstream
|
||||
The API server should then be run with TLS disabled. Edit the `argocd-server` deployment to add the
|
||||
`--insecure` flag to the argocd-server command, or simply set `server.insecure: "true"` in the `argocd-cmd-params-cm` ConfigMap [as described here](server-commands/additional-configuration-method.md).
|
||||
|
||||
Contour httpproxy CRD:
|
||||
|
||||
Using a contour httpproxy CRD allows you to use the same hostname for the GRPC and REST api.
|
||||
|
||||
```yaml
|
||||
apiVersion: projectcontour.io/v1
|
||||
kind: HTTPProxy
|
||||
metadata:
|
||||
name: argocd-server
|
||||
namespace: argocd
|
||||
spec:
|
||||
ingressClassName: contour
|
||||
virtualhost:
|
||||
fqdn: path.to.argocd.io
|
||||
tls:
|
||||
secretName: wildcard-tls
|
||||
routes:
|
||||
- conditions:
|
||||
- prefix: /
|
||||
- header:
|
||||
name: Content-Type
|
||||
contains: application/grpc
|
||||
services:
|
||||
- name: argocd-server
|
||||
port: 80
|
||||
protocol: h2c # allows for unencrypted http2 connections
|
||||
timeoutPolicy:
|
||||
response: 1h
|
||||
idle: 600s
|
||||
idleConnection: 600s
|
||||
- conditions:
|
||||
- prefix: /
|
||||
services:
|
||||
- name: argocd-server
|
||||
port: 80
|
||||
```
|
||||
|
||||
## [kubernetes/ingress-nginx](https://github.com/kubernetes/ingress-nginx)
|
||||
|
||||
### Option 1: SSL-Passthrough
|
||||
|
||||
@@ -70,6 +70,8 @@ Scraped at the `argocd-server-metrics:8083/metrics` endpoint.
|
||||
| `argocd_redis_request_total` | counter | Number of Kubernetes requests executed during application reconciliation. |
|
||||
| `grpc_server_handled_total` | counter | Total number of RPCs completed on the server, regardless of success or failure. |
|
||||
| `grpc_server_msg_sent_total` | counter | Total number of gRPC stream messages sent by the server. |
|
||||
| `argocd_proxy_extension_request_total` | counter | Number of requests sent to the configured proxy extensions. |
|
||||
| `argocd_proxy_extension_request_duration_seconds` | histogram | Request duration in seconds between the Argo CD API server and the proxy extension backend. |
|
||||
|
||||
## Repo Server Metrics
|
||||
Metrics about the Repo Server.
|
||||
@@ -79,6 +81,7 @@ Scraped at the `argocd-repo-server:8084/metrics` endpoint.
|
||||
|--------|:----:|-------------|
|
||||
| `argocd_git_request_duration_seconds` | histogram | Git requests duration seconds. |
|
||||
| `argocd_git_request_total` | counter | Number of git requests performed by repo server |
|
||||
| `argocd_git_fetch_fail_total` | counter | Number of git fetch requests failures by repo server |
|
||||
| `argocd_redis_request_duration_seconds` | histogram | Redis requests duration seconds. |
|
||||
| `argocd_redis_request_total` | counter | Number of Kubernetes requests executed during application reconciliation. |
|
||||
| `argocd_repo_pending_request_total` | gauge | Number of pending requests requiring repository lock |
|
||||
@@ -168,6 +171,8 @@ apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: argocd-redis-haproxy-metrics
|
||||
labels:
|
||||
release: prometheus-operator
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
@@ -176,7 +181,7 @@ spec:
|
||||
- port: http-exporter-port
|
||||
```
|
||||
|
||||
For notifications controller, you need to additionally add following:
|
||||
For notifications controller, you need to additionally add following:
|
||||
|
||||
```yaml
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
|
||||
@@ -62,8 +62,7 @@ slack:
|
||||
"short": true
|
||||
}
|
||||
{{range $index, $c := .app.status.conditions}}
|
||||
{{if not $index}},{{end}}
|
||||
{{if $index}},{{end}}
|
||||
,
|
||||
{
|
||||
"title": "{{$c.type}}",
|
||||
"value": "{{$c.message}}",
|
||||
@@ -90,8 +89,7 @@ teams:
|
||||
"value": "{{.app.status.sync.revision}}"
|
||||
}
|
||||
{{range $index, $c := .app.status.conditions}}
|
||||
{{if not $index}},{{end}}
|
||||
{{if $index}},{{end}}
|
||||
,
|
||||
{
|
||||
"name": "{{$c.type}}",
|
||||
"value": "{{$c.message}}"
|
||||
@@ -145,8 +143,7 @@ slack:
|
||||
"short": true
|
||||
}
|
||||
{{range $index, $c := .app.status.conditions}}
|
||||
{{if not $index}},{{end}}
|
||||
{{if $index}},{{end}}
|
||||
,
|
||||
{
|
||||
"title": "{{$c.type}}",
|
||||
"value": "{{$c.message}}",
|
||||
@@ -169,8 +166,7 @@ teams:
|
||||
"value": "{{.app.spec.source.repoURL}}"
|
||||
}
|
||||
{{range $index, $c := .app.status.conditions}}
|
||||
{{if not $index}},{{end}}
|
||||
{{if $index}},{{end}}
|
||||
,
|
||||
{
|
||||
"name": "{{$c.type}}",
|
||||
"value": "{{$c.message}}"
|
||||
@@ -224,8 +220,7 @@ slack:
|
||||
"short": true
|
||||
}
|
||||
{{range $index, $c := .app.status.conditions}}
|
||||
{{if not $index}},{{end}}
|
||||
{{if $index}},{{end}}
|
||||
,
|
||||
{
|
||||
"title": "{{$c.type}}",
|
||||
"value": "{{$c.message}}",
|
||||
@@ -252,8 +247,7 @@ teams:
|
||||
"value": "{{.app.spec.source.repoURL}}"
|
||||
}
|
||||
{{range $index, $c := .app.status.conditions}}
|
||||
{{if not $index}},{{end}}
|
||||
{{if $index}},{{end}}
|
||||
,
|
||||
{
|
||||
"name": "{{$c.type}}",
|
||||
"value": "{{$c.message}}"
|
||||
@@ -307,8 +301,7 @@ slack:
|
||||
"short": true
|
||||
}
|
||||
{{range $index, $c := .app.status.conditions}}
|
||||
{{if not $index}},{{end}}
|
||||
{{if $index}},{{end}}
|
||||
,
|
||||
{
|
||||
"title": "{{$c.type}}",
|
||||
"value": "{{$c.message}}",
|
||||
@@ -335,8 +328,7 @@ teams:
|
||||
"value": "{{.app.spec.source.repoURL}}"
|
||||
}
|
||||
{{range $index, $c := .app.status.conditions}}
|
||||
{{if not $index}},{{end}}
|
||||
{{if $index}},{{end}}
|
||||
,
|
||||
{
|
||||
"name": "{{$c.type}}",
|
||||
"value": "{{$c.message}}"
|
||||
@@ -394,8 +386,7 @@ slack:
|
||||
"short": true
|
||||
}
|
||||
{{range $index, $c := .app.status.conditions}}
|
||||
{{if not $index}},{{end}}
|
||||
{{if $index}},{{end}}
|
||||
,
|
||||
{
|
||||
"title": "{{$c.type}}",
|
||||
"value": "{{$c.message}}",
|
||||
@@ -418,8 +409,7 @@ teams:
|
||||
"value": "{{.app.spec.source.repoURL}}"
|
||||
}
|
||||
{{range $index, $c := .app.status.conditions}}
|
||||
{{if not $index}},{{end}}
|
||||
{{if $index}},{{end}}
|
||||
,
|
||||
{
|
||||
"name": "{{$c.type}}",
|
||||
"value": "{{$c.message}}"
|
||||
@@ -472,8 +462,7 @@ slack:
|
||||
"short": true
|
||||
}
|
||||
{{range $index, $c := .app.status.conditions}}
|
||||
{{if not $index}},{{end}}
|
||||
{{if $index}},{{end}}
|
||||
,
|
||||
{
|
||||
"title": "{{$c.type}}",
|
||||
"value": "{{$c.message}}",
|
||||
@@ -500,8 +489,7 @@ teams:
|
||||
"value": "{{.app.spec.source.repoURL}}"
|
||||
}
|
||||
{{range $index, $c := .app.status.conditions}}
|
||||
{{if not $index}},{{end}}
|
||||
{{if $index}},{{end}}
|
||||
,
|
||||
{
|
||||
"name": "{{$c.type}}",
|
||||
"value": "{{$c.message}}"
|
||||
|
||||
@@ -48,6 +48,16 @@ Transforms given GIT URL into HTTPs format.
|
||||
|
||||
Returns repository URL full name `(<owner>/<repoName>)`. Currently supports only Github, GitLab and Bitbucket.
|
||||
|
||||
<hr>
|
||||
**`repo.QueryEscape(s string) string`**
|
||||
|
||||
QueryEscape escapes the string, so it can be safely placed inside a URL
|
||||
|
||||
Example:
|
||||
```
|
||||
/projects/{{ call .repo.QueryEscape (call .repo.FullNameByRepoURL .app.status.RepoURL) }}/merge_requests
|
||||
```
|
||||
|
||||
<hr>
|
||||
**`repo.GetCommitMetadata(sha string) CommitMetadata`**
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ You should turn off "send_resolved" or you will receive unnecessary recovery not
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: <config-map-name>
|
||||
name: argocd-notifications-cm
|
||||
data:
|
||||
service.alertmanager: |
|
||||
targets:
|
||||
@@ -58,7 +58,7 @@ If your alertmanager has changed the default api, you can customize "apiPath".
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: <config-map-name>
|
||||
name: argocd-notifications-cm
|
||||
data:
|
||||
service.alertmanager: |
|
||||
targets:
|
||||
@@ -89,7 +89,7 @@ stringData:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: <config-map-name>
|
||||
name: argocd-notifications-cm
|
||||
data:
|
||||
service.alertmanager: |
|
||||
targets:
|
||||
@@ -110,7 +110,7 @@ data:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: <config-map-name>
|
||||
name: argocd-notifications-cm
|
||||
data:
|
||||
service.alertmanager: |
|
||||
targets:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# AWS SQS
|
||||
# AWS SQS
|
||||
|
||||
## Parameters
|
||||
|
||||
This notification service is capable of sending simple messages to AWS SQS queue.
|
||||
This notification service is capable of sending simple messages to AWS SQS queue.
|
||||
|
||||
* `queue` - name of the queue you are intending to send messages to. Can be overridden with target destination annotation.
|
||||
* `region` - region of the sqs queue can be provided via env variable AWS_DEFAULT_REGION
|
||||
@@ -30,7 +30,7 @@ metadata:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: <config-map-name>
|
||||
name: argocd-notifications-cm
|
||||
data:
|
||||
service.awssqs: |
|
||||
region: "us-east-2"
|
||||
@@ -63,7 +63,7 @@ stringData:
|
||||
|
||||
### Minimal configuration using AWS Env variables
|
||||
|
||||
Ensure following list of environment variables are injected via OIDC, or other method. And assuming SQS is local to the account.
|
||||
Ensure the following list of environment variables are injected via OIDC, or another method. And assuming SQS is local to the account.
|
||||
You may skip usage of secret for sensitive data and omit other parameters. (Setting parameters via ConfigMap takes precedent.)
|
||||
|
||||
Variables:
|
||||
@@ -89,7 +89,7 @@ metadata:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: <config-map-name>
|
||||
name: argocd-notifications-cm
|
||||
data:
|
||||
service.awssqs: |
|
||||
queue: "myqueue"
|
||||
@@ -104,3 +104,16 @@ data:
|
||||
- oncePer: obj.metadata.annotations["generation"]
|
||||
|
||||
```
|
||||
|
||||
## FIFO SQS Queues
|
||||
|
||||
FIFO queues require a [MessageGroupId](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessage.html#SQS-SendMessage-request-MessageGroupId) to be sent along with every message, every message with a matching MessageGroupId will be processed one by one in order.
|
||||
|
||||
To send to a FIFO SQS Queue you must include a `messageGroupId` in the template such as in the example below:
|
||||
|
||||
```yaml
|
||||
template.deployment-ready: |
|
||||
message: |
|
||||
Deployment {{.obj.metadata.name}} is ready!
|
||||
messageGroupId: {{.obj.metadata.name}}-deployment
|
||||
```
|
||||
|
||||
@@ -20,7 +20,7 @@ The following snippet contains sample Gmail service configuration:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: <config-map-name>
|
||||
name: argocd-notifications-cm
|
||||
data:
|
||||
service.email.gmail: |
|
||||
username: $email-username
|
||||
@@ -36,7 +36,7 @@ Without authentication:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: <config-map-name>
|
||||
name: argocd-notifications-cm
|
||||
data:
|
||||
service.email.example: |
|
||||
host: smtp.example.com
|
||||
@@ -52,7 +52,7 @@ data:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: <config-map-name>
|
||||
name: argocd-notifications-cm
|
||||
data:
|
||||
template.app-sync-succeeded: |
|
||||
email:
|
||||
|
||||
@@ -24,7 +24,7 @@ in `argocd-notifications-cm` ConfigMap
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: <config-map-name>
|
||||
name: argocd-notifications-cm
|
||||
data:
|
||||
service.github: |
|
||||
appID: <app-id>
|
||||
@@ -76,6 +76,7 @@ template.app-deployed: |
|
||||
logURL: "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true"
|
||||
requiredContexts: []
|
||||
autoMerge: true
|
||||
transientEnvironment: false
|
||||
pullRequestComment:
|
||||
content: |
|
||||
Application {{.app.metadata.name}} is now running new version of deployments manifests.
|
||||
|
||||
@@ -19,7 +19,7 @@ The Google Chat notification service send message notifications to a google chat
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: <config-map-name>
|
||||
name: argocd-notifications-cm
|
||||
data:
|
||||
service.googlechat: |
|
||||
webhooks:
|
||||
|
||||
@@ -21,7 +21,7 @@ Available parameters :
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: <config-map-name>
|
||||
name: argocd-notifications-cm
|
||||
data:
|
||||
service.grafana: |
|
||||
apiUrl: https://grafana.example.com/api
|
||||
|
||||
@@ -19,7 +19,7 @@ in `argocd-notifications-cm` ConfigMap
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: <config-map-name>
|
||||
name: argocd-notifications-cm
|
||||
data:
|
||||
service.mattermost: |
|
||||
apiURL: <api-url>
|
||||
|
||||