mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-02-25 03:58:48 +01:00
Compare commits
107 Commits
v2.4.18
...
release-2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eef1ddfd8b | ||
|
|
38e5b676c9 | ||
|
|
bea956674d | ||
|
|
ed0682d2b5 | ||
|
|
598f79236a | ||
|
|
bdc043cadc | ||
|
|
63f9622b00 | ||
|
|
4375305a20 | ||
|
|
2cc2f9fe21 | ||
|
|
f8e99a2a13 | ||
|
|
4d37861374 | ||
|
|
b0782518d6 | ||
|
|
afe352ba20 | ||
|
|
9a9498958b | ||
|
|
7f666977e6 | ||
|
|
26e07509e6 | ||
|
|
c1967d0b21 | ||
|
|
65e4e429c4 | ||
|
|
1cc8eaa49a | ||
|
|
94c73e62f9 | ||
|
|
27ecf4b3ff | ||
|
|
7b72e65562 | ||
|
|
178aa6ad0f | ||
|
|
71bd164eb6 | ||
|
|
e7ca215316 | ||
|
|
aa25c05156 | ||
|
|
0b8e4b356c | ||
|
|
8a7d291f99 | ||
|
|
bb0e260971 | ||
|
|
a0a3246421 | ||
|
|
a6c15a5b3f | ||
|
|
52f4a1d32a | ||
|
|
148830b788 | ||
|
|
aed9436d1b | ||
|
|
9d3d7808cb | ||
|
|
fe6d9c9fac | ||
|
|
2895869cec | ||
|
|
dbd03d07d2 | ||
|
|
b49f9fbc74 | ||
|
|
f8c7c88abf | ||
|
|
f102988793 | ||
|
|
2c52907304 | ||
|
|
1d6f8b113c | ||
|
|
5bfcff8631 | ||
|
|
55289991c6 | ||
|
|
b007073231 | ||
|
|
536d1d0e10 | ||
|
|
dd44083d78 | ||
|
|
db031bbe94 | ||
|
|
ac0b3e76c0 | ||
|
|
a9289d2381 | ||
|
|
4be0ce491d | ||
|
|
e86cf5d705 | ||
|
|
8f0d27705e | ||
|
|
5831a573aa | ||
|
|
90869357fb | ||
|
|
d0551e4c1a | ||
|
|
402b38dc7d | ||
|
|
eadd25e7ed | ||
|
|
3f98ee249b | ||
|
|
cafb09a8a5 | ||
|
|
f7b9fa32e4 | ||
|
|
8536a84775 | ||
|
|
4bc13fbb25 | ||
|
|
fe6472db05 | ||
|
|
efb0ceca33 | ||
|
|
717ac4eec1 | ||
|
|
6cb2a600b0 | ||
|
|
2eb5656842 | ||
|
|
5c6f8d5460 | ||
|
|
45c089b48c | ||
|
|
11ba4834ad | ||
|
|
8f4678ee1f | ||
|
|
68f58c956a | ||
|
|
5b3bfc746c | ||
|
|
20c63babca | ||
|
|
0775c6e726 | ||
|
|
0ba9817cc2 | ||
|
|
cac46bf3a6 | ||
|
|
e89bf08452 | ||
|
|
4fa0627dd8 | ||
|
|
4665f19f9a | ||
|
|
32ab1d906f | ||
|
|
64b3f0f1f4 | ||
|
|
70276136b1 | ||
|
|
0051144468 | ||
|
|
df55641842 | ||
|
|
7cde38c52f | ||
|
|
e9a2102593 | ||
|
|
56cba50d69 | ||
|
|
556565f167 | ||
|
|
f625165b33 | ||
|
|
d11ee714b8 | ||
|
|
3aa191d777 | ||
|
|
3487d4789d | ||
|
|
9c9fd9685a | ||
|
|
bda913c1f2 | ||
|
|
e37feab905 | ||
|
|
e380dd1ade | ||
|
|
6c88a166d8 | ||
|
|
f007ddffc9 | ||
|
|
af7b29d922 | ||
|
|
a10a54782a | ||
|
|
20e4b8806d | ||
|
|
2626453cfc | ||
|
|
5d6600ed9d | ||
|
|
0f3f8db000 |
75
.github/workflows/ci-build.yaml
vendored
75
.github/workflows/ci-build.yaml
vendored
@@ -9,6 +9,7 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'release-*'
|
||||
|
||||
env:
|
||||
# Golang version to use across CI steps
|
||||
@@ -27,9 +28,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@d0a58c1c4d2b25278816e339b944508c875f3613 # v3.4.0
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Download all Go modules
|
||||
@@ -45,13 +46,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@d0a58c1c4d2b25278816e339b944508c875f3613 # v3.4.0
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Restore go build cache
|
||||
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
||||
@@ -69,9 +70,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@d0a58c1c4d2b25278816e339b944508c875f3613 # v3.4.0
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Run golangci-lint
|
||||
@@ -92,11 +93,11 @@ jobs:
|
||||
- name: Create checkout directory
|
||||
run: mkdir -p ~/go/src/github.com/argoproj
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
- name: Create symlink in GOPATH
|
||||
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@d0a58c1c4d2b25278816e339b944508c875f3613 # v3.4.0
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Install required packages
|
||||
@@ -116,13 +117,17 @@ jobs:
|
||||
run: |
|
||||
echo "/usr/local/bin" >> $GITHUB_PATH
|
||||
- name: Restore go build cache
|
||||
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
||||
- name: Install all tools required for building & testing
|
||||
run: |
|
||||
make install-test-tools-local
|
||||
# We install kustomize in the dist directory
|
||||
- name: Add dist to PATH
|
||||
run: |
|
||||
echo "/home/runner/work/argo-cd/argo-cd/dist" >> $GITHUB_PATH
|
||||
- name: Setup git username and email
|
||||
run: |
|
||||
git config --global user.name "John Doe"
|
||||
@@ -133,12 +138,12 @@ jobs:
|
||||
- name: Run all unit tests
|
||||
run: make test-local
|
||||
- name: Generate code coverage artifacts
|
||||
uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: code-coverage
|
||||
path: coverage.out
|
||||
- name: Generate test results artifacts
|
||||
uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: test-results
|
||||
path: test-results/
|
||||
@@ -155,11 +160,11 @@ jobs:
|
||||
- name: Create checkout directory
|
||||
run: mkdir -p ~/go/src/github.com/argoproj
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
- name: Create symlink in GOPATH
|
||||
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@d0a58c1c4d2b25278816e339b944508c875f3613 # v3.4.0
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Install required packages
|
||||
@@ -179,13 +184,17 @@ jobs:
|
||||
run: |
|
||||
echo "/usr/local/bin" >> $GITHUB_PATH
|
||||
- name: Restore go build cache
|
||||
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
||||
- name: Install all tools required for building & testing
|
||||
run: |
|
||||
make install-test-tools-local
|
||||
# We install kustomize in the dist directory
|
||||
- name: Add dist to PATH
|
||||
run: |
|
||||
echo "/home/runner/work/argo-cd/argo-cd/dist" >> $GITHUB_PATH
|
||||
- name: Setup git username and email
|
||||
run: |
|
||||
git config --global user.name "John Doe"
|
||||
@@ -196,7 +205,7 @@ jobs:
|
||||
- name: Run all unit tests
|
||||
run: make test-race-local
|
||||
- name: Generate test results artifacts
|
||||
uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: race-results
|
||||
path: test-results/
|
||||
@@ -206,9 +215,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@d0a58c1c4d2b25278816e339b944508c875f3613 # v3.4.0
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Create symlink in GOPATH
|
||||
@@ -232,6 +241,10 @@ jobs:
|
||||
make install-codegen-tools-local
|
||||
make install-go-tools-local
|
||||
working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
|
||||
# We install kustomize in the dist directory
|
||||
- name: Add dist to PATH
|
||||
run: |
|
||||
echo "/home/runner/work/argo-cd/argo-cd/dist" >> $GITHUB_PATH
|
||||
- name: Run codegen
|
||||
run: |
|
||||
set -x
|
||||
@@ -250,14 +263,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # v3.5.1
|
||||
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
||||
with:
|
||||
node-version: '12.18.4'
|
||||
- name: Restore node dependency cache
|
||||
id: cache-dependencies
|
||||
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
with:
|
||||
path: ui/node_modules
|
||||
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
|
||||
@@ -287,12 +300,12 @@ jobs:
|
||||
sonar_secret: ${{ secrets.SONAR_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Restore node dependency cache
|
||||
id: cache-dependencies
|
||||
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
with:
|
||||
path: ui/node_modules
|
||||
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
|
||||
@@ -303,11 +316,11 @@ jobs:
|
||||
run: |
|
||||
mkdir -p test-results
|
||||
- name: Get code coverage artifiact
|
||||
uses: actions/download-artifact@9782bd6a9848b53b110e712e20e42d89988822b7 # v3.0.1
|
||||
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
|
||||
with:
|
||||
name: code-coverage
|
||||
- name: Get test result artifact
|
||||
uses: actions/download-artifact@9782bd6a9848b53b110e712e20e42d89988822b7 # v3.0.1
|
||||
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
|
||||
with:
|
||||
name: test-results
|
||||
path: test-results
|
||||
@@ -365,9 +378,9 @@ jobs:
|
||||
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@d0a58c1c4d2b25278816e339b944508c875f3613 # v3.4.0
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: GH actions workaround - Kill XSP4 process
|
||||
@@ -393,7 +406,7 @@ jobs:
|
||||
sudo chown runner $HOME/.kube/config
|
||||
kubectl version
|
||||
- name: Restore go build cache
|
||||
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
||||
@@ -419,9 +432,9 @@ jobs:
|
||||
git config --global user.email "john.doe@example.com"
|
||||
- name: Pull Docker image required for tests
|
||||
run: |
|
||||
docker pull ghcr.io/dexidp/dex:v2.35.3
|
||||
docker pull ghcr.io/dexidp/dex:v2.36.0
|
||||
docker pull argoproj/argo-cd-ci-builder:v1.0.0
|
||||
docker pull redis:7.0.4-alpine
|
||||
docker pull redis:7.0.8-alpine
|
||||
- name: Create target directory for binaries in the build-process
|
||||
run: |
|
||||
mkdir -p dist
|
||||
@@ -449,7 +462,7 @@ jobs:
|
||||
set -x
|
||||
make test-e2e-local
|
||||
- name: Upload e2e-server logs
|
||||
uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1
|
||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
with:
|
||||
name: e2e-server-k8s${{ matrix.k3s-version }}.log
|
||||
path: /tmp/e2e-server.log
|
||||
|
||||
3
.github/workflows/codeql.yml
vendored
3
.github/workflows/codeql.yml
vendored
@@ -5,6 +5,7 @@ on:
|
||||
# Secrets aren't available for dependabot on push. https://docs.github.com/en/enterprise-cloud@latest/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/troubleshooting-the-codeql-workflow#error-403-resource-not-accessible-by-integration-when-using-dependabot
|
||||
branches-ignore:
|
||||
- 'dependabot/**'
|
||||
- 'cherry-pick-*'
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 19 * * 0'
|
||||
@@ -29,7 +30,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
21
.github/workflows/image.yaml
vendored
21
.github/workflows/image.yaml
vendored
@@ -28,10 +28,10 @@ jobs:
|
||||
env:
|
||||
GOPATH: /home/runner/work/argo-cd/argo-cd
|
||||
steps:
|
||||
- uses: actions/setup-go@d0a58c1c4d2b25278816e339b944508c875f3613 # v3.4.0
|
||||
- uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
|
||||
- uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
with:
|
||||
path: src/github.com/argoproj/argo-cd
|
||||
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
# build
|
||||
- uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0
|
||||
- uses: docker/setup-buildx-action@8c0edbc76e98fa90f69d9a2c020dcb50019dc325 # v2.2.1
|
||||
- uses: docker/setup-buildx-action@f03ac48505955848960e80bbb68046aa35c7b9e7 # v2.4.1
|
||||
|
||||
- name: Setup cache for argocd-ui docker layer
|
||||
uses: actions/cache@v3
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
IMAGE_PLATFORMS=linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
|
||||
fi
|
||||
echo "Building image for platforms: $IMAGE_PLATFORMS"
|
||||
docker buildx build --platform $IMAGE_PLATFORMS --push="${{ github.event_name == 'push' }}" \
|
||||
docker buildx build --platform $IMAGE_PLATFORMS --sbom=false --provenance=false --push="${{ github.event_name == 'push' }}" \
|
||||
--cache-from "type=local,src=/tmp/.buildx-cache" \
|
||||
-t ghcr.io/argoproj/argocd:${{ steps.image.outputs.tag }} \
|
||||
-t quay.io/argoproj/argocd:latest .
|
||||
@@ -117,13 +117,20 @@ jobs:
|
||||
|
||||
# sign container images
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@9becc617647dfa20ae7b1151972e9b3a2c338a2b # v2.8.1
|
||||
uses: sigstore/cosign-installer@c3667d99424e7e6047999fb6246c0da843953c65 # v3.0.1
|
||||
with:
|
||||
cosign-release: 'v1.13.0'
|
||||
cosign-release: 'v1.13.1'
|
||||
|
||||
- name: Install crane to get digest of image
|
||||
uses: imjasonh/setup-crane@00c9e93efa4e1138c9a7a5c594acd6c75a2fbf0c
|
||||
|
||||
- name: Get digest of image
|
||||
run: |
|
||||
echo "IMAGE_DIGEST=$(crane digest quay.io/argoproj/argocd:latest)" >> $GITHUB_ENV
|
||||
|
||||
- name: Sign Argo CD latest image
|
||||
run: |
|
||||
cosign sign --key env://COSIGN_PRIVATE_KEY quay.io/argoproj/argocd:latest
|
||||
cosign sign --key env://COSIGN_PRIVATE_KEY quay.io/argoproj/argocd@${{ env.IMAGE_DIGEST }}
|
||||
# Displays the public key to share.
|
||||
cosign public-key --key env://COSIGN_PRIVATE_KEY
|
||||
env:
|
||||
|
||||
29
.github/workflows/release.yaml
vendored
29
.github/workflows/release.yaml
vendored
@@ -12,7 +12,7 @@ on:
|
||||
- "!release-v0*"
|
||||
|
||||
env:
|
||||
GOLANG_VERSION: '1.18'
|
||||
GOLANG_VERSION: '1.18'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
GIT_EMAIL: argoproj@gmail.com
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
echo "RELEASE_NOTES=${RELEASE_NOTES}" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@d0a58c1c4d2b25278816e339b944508c875f3613 # v3.4.0
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
|
||||
@@ -177,6 +177,10 @@ jobs:
|
||||
run: |
|
||||
set -ue
|
||||
make install-codegen-tools-local
|
||||
|
||||
# We install kustomize in the dist directory
|
||||
echo "/home/runner/work/argo-cd/argo-cd/dist" >> $GITHUB_PATH
|
||||
|
||||
make manifests-local VERSION=${TARGET_VERSION}
|
||||
git diff
|
||||
git commit manifests/ -m "Bump version to ${TARGET_VERSION}"
|
||||
@@ -201,13 +205,13 @@ jobs:
|
||||
if: ${{ env.DRY_RUN != 'true' }}
|
||||
|
||||
- uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0
|
||||
- uses: docker/setup-buildx-action@8c0edbc76e98fa90f69d9a2c020dcb50019dc325 # v2.2.1
|
||||
- uses: docker/setup-buildx-action@f03ac48505955848960e80bbb68046aa35c7b9e7 # v2.4.1
|
||||
- name: Build and push Docker image for release
|
||||
run: |
|
||||
set -ue
|
||||
git clean -fd
|
||||
mkdir -p dist/
|
||||
docker buildx build --platform linux/amd64,linux/arm64,linux/s390x,linux/ppc64le --push -t ${IMAGE_NAMESPACE}/argocd:v${TARGET_VERSION} -t argoproj/argocd:v${TARGET_VERSION} .
|
||||
docker buildx build --platform linux/amd64,linux/arm64,linux/s390x,linux/ppc64le --sbom=false --provenance=false --push -t ${IMAGE_NAMESPACE}/argocd:v${TARGET_VERSION} -t argoproj/argocd:v${TARGET_VERSION} .
|
||||
make release-cli
|
||||
make checksums
|
||||
chmod +x ./dist/argocd-linux-amd64
|
||||
@@ -215,13 +219,20 @@ jobs:
|
||||
if: ${{ env.DRY_RUN != 'true' }}
|
||||
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@9becc617647dfa20ae7b1151972e9b3a2c338a2b # v2.8.1
|
||||
uses: sigstore/cosign-installer@c3667d99424e7e6047999fb6246c0da843953c65 # v3.0.1
|
||||
with:
|
||||
cosign-release: 'v1.13.0'
|
||||
cosign-release: 'v1.13.1'
|
||||
|
||||
- name: Install crane to get digest of image
|
||||
uses: imjasonh/setup-crane@00c9e93efa4e1138c9a7a5c594acd6c75a2fbf0c
|
||||
|
||||
- name: Get digest of image
|
||||
run: |
|
||||
echo "IMAGE_DIGEST=$(crane digest quay.io/argoproj/argocd:v${TARGET_VERSION})" >> $GITHUB_ENV
|
||||
|
||||
- name: Sign Argo CD container images and assets
|
||||
run: |
|
||||
cosign sign --key env://COSIGN_PRIVATE_KEY ${IMAGE_NAMESPACE}/argocd:v${TARGET_VERSION}
|
||||
cosign sign --key env://COSIGN_PRIVATE_KEY ${IMAGE_NAMESPACE}/argocd@${{ env.IMAGE_DIGEST }}
|
||||
cosign sign-blob --key env://COSIGN_PRIVATE_KEY ./dist/argocd-${TARGET_VERSION}-checksums.txt > ./dist/argocd-${TARGET_VERSION}-checksums.sig
|
||||
# Retrieves the public key to release as an asset
|
||||
cosign public-key --key env://COSIGN_PRIVATE_KEY > ./dist/argocd-cosign.pub
|
||||
@@ -264,7 +275,7 @@ jobs:
|
||||
SIGS_BOM_VERSION: v0.2.1
|
||||
# comma delimited list of project relative folders to inspect for package
|
||||
# managers (gomod, yarn, npm).
|
||||
PROJECT_FOLDERS: ".,./ui"
|
||||
PROJECT_FOLDERS: ".,./ui"
|
||||
# full qualified name of the docker image to be inspected
|
||||
DOCKER_IMAGE: ${{env.IMAGE_NAMESPACE}}/argocd:v${{env.TARGET_VERSION}}
|
||||
run: |
|
||||
|
||||
1
USERS.md
1
USERS.md
@@ -82,6 +82,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [IITS-Consulting](https://iits-consulting.de)
|
||||
1. [imaware](https://imaware.health)
|
||||
1. [Index Exchange](https://www.indexexchange.com/)
|
||||
1. [Info Support](https://www.infosupport.com/)
|
||||
1. [InsideBoard](https://www.insideboard.com)
|
||||
1. [Intuit](https://www.intuit.com/)
|
||||
1. [Joblift](https://joblift.com/)
|
||||
|
||||
180
applicationset/controllers/requeue_after_test.go
Normal file
180
applicationset/controllers/requeue_after_test.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
dynfake "k8s.io/client-go/dynamic/fake"
|
||||
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/generators"
|
||||
argoappsetv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/applicationset/v1alpha1"
|
||||
)
|
||||
|
||||
func TestRequeueAfter(t *testing.T) {
|
||||
mockServer := argoCDServiceMock{}
|
||||
ctx := context.Background()
|
||||
scheme := runtime.NewScheme()
|
||||
err := argoappsetv1alpha1.AddToScheme(scheme)
|
||||
assert.Nil(t, err)
|
||||
gvrToListKind := map[schema.GroupVersionResource]string{{
|
||||
Group: "mallard.io",
|
||||
Version: "v1",
|
||||
Resource: "ducks",
|
||||
}: "DuckList"}
|
||||
appClientset := kubefake.NewSimpleClientset()
|
||||
k8sClient := fake.NewClientBuilder().Build()
|
||||
duckType := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v2quack",
|
||||
"kind": "Duck",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "mightyduck",
|
||||
"namespace": "namespace",
|
||||
"labels": map[string]interface{}{"duck": "all-species"},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"decisions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"clusterName": "staging-01",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"clusterName": "production-01",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, duckType)
|
||||
|
||||
terminalGenerators := map[string]generators.Generator{
|
||||
"List": generators.NewListGenerator(),
|
||||
"Clusters": generators.NewClusterGenerator(k8sClient, ctx, appClientset, "argocd"),
|
||||
"Git": generators.NewGitGenerator(mockServer),
|
||||
"SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build()),
|
||||
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, fakeDynClient, appClientset, "argocd"),
|
||||
"PullRequest": generators.NewPullRequestGenerator(k8sClient),
|
||||
}
|
||||
|
||||
nestedGenerators := map[string]generators.Generator{
|
||||
"List": terminalGenerators["List"],
|
||||
"Clusters": terminalGenerators["Clusters"],
|
||||
"Git": terminalGenerators["Git"],
|
||||
"SCMProvider": terminalGenerators["SCMProvider"],
|
||||
"ClusterDecisionResource": terminalGenerators["ClusterDecisionResource"],
|
||||
"PullRequest": terminalGenerators["PullRequest"],
|
||||
"Matrix": generators.NewMatrixGenerator(terminalGenerators),
|
||||
"Merge": generators.NewMergeGenerator(terminalGenerators),
|
||||
}
|
||||
|
||||
topLevelGenerators := map[string]generators.Generator{
|
||||
"List": terminalGenerators["List"],
|
||||
"Clusters": terminalGenerators["Clusters"],
|
||||
"Git": terminalGenerators["Git"],
|
||||
"SCMProvider": terminalGenerators["SCMProvider"],
|
||||
"ClusterDecisionResource": terminalGenerators["ClusterDecisionResource"],
|
||||
"PullRequest": terminalGenerators["PullRequest"],
|
||||
"Matrix": generators.NewMatrixGenerator(nestedGenerators),
|
||||
"Merge": generators.NewMergeGenerator(nestedGenerators),
|
||||
}
|
||||
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).Build()
|
||||
r := ApplicationSetReconciler{
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(0),
|
||||
Generators: topLevelGenerators,
|
||||
}
|
||||
|
||||
type args struct {
|
||||
appset *argoappsetv1alpha1.ApplicationSet
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want time.Duration
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{name: "Cluster", args: args{appset: &argoappsetv1alpha1.ApplicationSet{
|
||||
Spec: argoappsetv1alpha1.ApplicationSetSpec{
|
||||
Generators: []argoappsetv1alpha1.ApplicationSetGenerator{{Clusters: &argoappsetv1alpha1.ClusterGenerator{}}},
|
||||
},
|
||||
}}, want: generators.NoRequeueAfter, wantErr: assert.NoError},
|
||||
{name: "ClusterMergeNested", args: args{&argoappsetv1alpha1.ApplicationSet{
|
||||
Spec: argoappsetv1alpha1.ApplicationSetSpec{
|
||||
Generators: []argoappsetv1alpha1.ApplicationSetGenerator{
|
||||
{Clusters: &argoappsetv1alpha1.ClusterGenerator{}},
|
||||
{Merge: &argoappsetv1alpha1.MergeGenerator{
|
||||
Generators: []argoappsetv1alpha1.ApplicationSetNestedGenerator{
|
||||
{
|
||||
Clusters: &argoappsetv1alpha1.ClusterGenerator{},
|
||||
Git: &argoappsetv1alpha1.GitGenerator{},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}}, want: generators.DefaultRequeueAfterSeconds, wantErr: assert.NoError},
|
||||
{name: "ClusterMatrixNested", args: args{&argoappsetv1alpha1.ApplicationSet{
|
||||
Spec: argoappsetv1alpha1.ApplicationSetSpec{
|
||||
Generators: []argoappsetv1alpha1.ApplicationSetGenerator{
|
||||
{Clusters: &argoappsetv1alpha1.ClusterGenerator{}},
|
||||
{Matrix: &argoappsetv1alpha1.MatrixGenerator{
|
||||
Generators: []argoappsetv1alpha1.ApplicationSetNestedGenerator{
|
||||
{
|
||||
Clusters: &argoappsetv1alpha1.ClusterGenerator{},
|
||||
Git: &argoappsetv1alpha1.GitGenerator{},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}}, want: generators.DefaultRequeueAfterSeconds, wantErr: assert.NoError},
|
||||
{name: "ListGenerator", args: args{appset: &argoappsetv1alpha1.ApplicationSet{
|
||||
Spec: argoappsetv1alpha1.ApplicationSetSpec{
|
||||
Generators: []argoappsetv1alpha1.ApplicationSetGenerator{{List: &argoappsetv1alpha1.ListGenerator{}}},
|
||||
},
|
||||
}}, want: generators.NoRequeueAfter, wantErr: assert.NoError},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, r.getMinRequeueAfter(tt.args.appset), "getMinRequeueAfter(%v)", tt.args.appset)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type argoCDServiceMock struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (a argoCDServiceMock) GetApps(ctx context.Context, repoURL string, revision string) ([]string, error) {
|
||||
args := a.mock.Called(ctx, repoURL, revision)
|
||||
|
||||
return args.Get(0).([]string), args.Error(1)
|
||||
}
|
||||
|
||||
func (a argoCDServiceMock) GetFiles(ctx context.Context, repoURL string, revision string, pattern string) (map[string][]byte, error) {
|
||||
args := a.mock.Called(ctx, repoURL, revision, pattern)
|
||||
|
||||
return args.Get(0).(map[string][]byte), args.Error(1)
|
||||
}
|
||||
|
||||
func (a argoCDServiceMock) GetFileContent(ctx context.Context, repoURL string, revision string, path string) ([]byte, error) {
|
||||
args := a.mock.Called(ctx, repoURL, revision, path)
|
||||
|
||||
return args.Get(0).([]byte), args.Error(1)
|
||||
}
|
||||
|
||||
func (a argoCDServiceMock) GetDirectories(ctx context.Context, repoURL string, revision string) ([]string, error) {
|
||||
args := a.mock.Called(ctx, repoURL, revision)
|
||||
return args.Get(0).([]string), args.Error(1)
|
||||
}
|
||||
@@ -51,6 +51,8 @@ func NewClusterGenerator(c client.Client, ctx context.Context, clientset kuberne
|
||||
return g
|
||||
}
|
||||
|
||||
// GetRequeueAfter never requeue the cluster generator because the `clusterSecretEventHandler` will requeue the appsets
|
||||
// when the cluster secrets change
|
||||
func (g *ClusterGenerator) GetRequeueAfter(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) time.Duration {
|
||||
return NoRequeueAfter
|
||||
}
|
||||
|
||||
@@ -67,28 +67,13 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
|
||||
}
|
||||
|
||||
func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]string, error) {
|
||||
var matrix *argoprojiov1alpha1.MatrixGenerator
|
||||
if appSetBaseGenerator.Matrix != nil {
|
||||
// Since nested matrix generator is represented as a JSON object in the CRD, we unmarshall it back to a Go struct here.
|
||||
nestedMatrix, err := argoprojiov1alpha1.ToNestedMatrixGenerator(appSetBaseGenerator.Matrix)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshall nested matrix generator: %v", err)
|
||||
}
|
||||
if nestedMatrix != nil {
|
||||
matrix = nestedMatrix.ToMatrixGenerator()
|
||||
}
|
||||
matrixGen, err := getMatrixGenerator(appSetBaseGenerator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mergeGenerator *argoprojiov1alpha1.MergeGenerator
|
||||
if appSetBaseGenerator.Merge != nil {
|
||||
// Since nested merge generator is represented as a JSON object in the CRD, we unmarshall it back to a Go struct here.
|
||||
nestedMerge, err := argoprojiov1alpha1.ToNestedMergeGenerator(appSetBaseGenerator.Merge)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshall nested merge generator: %v", err)
|
||||
}
|
||||
if nestedMerge != nil {
|
||||
mergeGenerator = nestedMerge.ToMergeGenerator()
|
||||
}
|
||||
mergeGen, err := getMergeGenerator(appSetBaseGenerator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t, err := Transform(
|
||||
@@ -99,8 +84,8 @@ func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Appli
|
||||
SCMProvider: appSetBaseGenerator.SCMProvider,
|
||||
ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource,
|
||||
PullRequest: appSetBaseGenerator.PullRequest,
|
||||
Matrix: matrix,
|
||||
Merge: mergeGenerator,
|
||||
Matrix: matrixGen,
|
||||
Merge: mergeGen,
|
||||
},
|
||||
m.supportedGenerators,
|
||||
argoprojiov1alpha1.ApplicationSetTemplate{},
|
||||
@@ -128,10 +113,15 @@ func (m *MatrixGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
|
||||
var found bool
|
||||
|
||||
for _, r := range appSetGenerator.Matrix.Generators {
|
||||
matrixGen, _ := getMatrixGenerator(r)
|
||||
mergeGen, _ := getMergeGenerator(r)
|
||||
base := &argoprojiov1alpha1.ApplicationSetGenerator{
|
||||
List: r.List,
|
||||
Clusters: r.Clusters,
|
||||
Git: r.Git,
|
||||
List: r.List,
|
||||
Clusters: r.Clusters,
|
||||
Git: r.Git,
|
||||
PullRequest: r.PullRequest,
|
||||
Matrix: matrixGen,
|
||||
Merge: mergeGen,
|
||||
}
|
||||
generators := GetRelevantGenerators(base, m.supportedGenerators)
|
||||
|
||||
@@ -152,6 +142,17 @@ func (m *MatrixGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
|
||||
|
||||
}
|
||||
|
||||
func getMatrixGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MatrixGenerator, error) {
|
||||
if r.Matrix == nil {
|
||||
return nil, nil
|
||||
}
|
||||
matrix, err := argoprojiov1alpha1.ToNestedMatrixGenerator(r.Matrix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return matrix.ToMatrixGenerator(), nil
|
||||
}
|
||||
|
||||
func (m *MatrixGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
|
||||
return &appSetGenerator.Matrix.Template
|
||||
}
|
||||
|
||||
@@ -191,6 +191,8 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
|
||||
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url"}`)}},
|
||||
}
|
||||
|
||||
pullRequestGenerator := &argoprojiov1alpha1.PullRequestGenerator{}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
|
||||
@@ -223,6 +225,31 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
|
||||
gitGetRequeueAfter: time.Duration(1),
|
||||
expected: time.Duration(1),
|
||||
},
|
||||
{
|
||||
name: "returns the minimal time for pull request",
|
||||
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
|
||||
{
|
||||
Git: gitGenerator,
|
||||
},
|
||||
{
|
||||
PullRequest: pullRequestGenerator,
|
||||
},
|
||||
},
|
||||
gitGetRequeueAfter: time.Duration(15 * time.Second),
|
||||
expected: time.Duration(15 * time.Second),
|
||||
},
|
||||
{
|
||||
name: "returns the default time if no requeueAfterSeconds is provided",
|
||||
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
|
||||
{
|
||||
Git: gitGenerator,
|
||||
},
|
||||
{
|
||||
PullRequest: pullRequestGenerator,
|
||||
},
|
||||
},
|
||||
expected: time.Duration(30 * time.Minute),
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
@@ -233,16 +260,18 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
|
||||
|
||||
for _, g := range testCaseCopy.baseGenerators {
|
||||
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
|
||||
Git: g.Git,
|
||||
List: g.List,
|
||||
Git: g.Git,
|
||||
List: g.List,
|
||||
PullRequest: g.PullRequest,
|
||||
}
|
||||
mock.On("GetRequeueAfter", &gitGeneratorSpec).Return(testCaseCopy.gitGetRequeueAfter, nil)
|
||||
}
|
||||
|
||||
var matrixGenerator = NewMatrixGenerator(
|
||||
map[string]Generator{
|
||||
"Git": mock,
|
||||
"List": &ListGenerator{},
|
||||
"Git": mock,
|
||||
"List": &ListGenerator{},
|
||||
"PullRequest": &PullRequestGenerator{},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -127,27 +127,13 @@ func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]string) (
|
||||
|
||||
// getParams get the parameters generated by this generator.
|
||||
func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]string, error) {
|
||||
|
||||
var matrix *argoprojiov1alpha1.MatrixGenerator
|
||||
if appSetBaseGenerator.Matrix != nil {
|
||||
nestedMatrix, err := argoprojiov1alpha1.ToNestedMatrixGenerator(appSetBaseGenerator.Matrix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nestedMatrix != nil {
|
||||
matrix = nestedMatrix.ToMatrixGenerator()
|
||||
}
|
||||
matrixGen, err := getMatrixGenerator(appSetBaseGenerator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mergeGenerator *argoprojiov1alpha1.MergeGenerator
|
||||
if appSetBaseGenerator.Merge != nil {
|
||||
nestedMerge, err := argoprojiov1alpha1.ToNestedMergeGenerator(appSetBaseGenerator.Merge)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nestedMerge != nil {
|
||||
mergeGenerator = nestedMerge.ToMergeGenerator()
|
||||
}
|
||||
mergeGen, err := getMergeGenerator(appSetBaseGenerator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t, err := Transform(
|
||||
@@ -158,8 +144,8 @@ func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Applic
|
||||
SCMProvider: appSetBaseGenerator.SCMProvider,
|
||||
ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource,
|
||||
PullRequest: appSetBaseGenerator.PullRequest,
|
||||
Matrix: matrix,
|
||||
Merge: mergeGenerator,
|
||||
Matrix: matrixGen,
|
||||
Merge: mergeGen,
|
||||
},
|
||||
m.supportedGenerators,
|
||||
argoprojiov1alpha1.ApplicationSetTemplate{},
|
||||
@@ -185,10 +171,15 @@ func (m *MergeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.App
|
||||
var found bool
|
||||
|
||||
for _, r := range appSetGenerator.Merge.Generators {
|
||||
matrixGen, _ := getMatrixGenerator(r)
|
||||
mergeGen, _ := getMergeGenerator(r)
|
||||
base := &argoprojiov1alpha1.ApplicationSetGenerator{
|
||||
List: r.List,
|
||||
Clusters: r.Clusters,
|
||||
Git: r.Git,
|
||||
List: r.List,
|
||||
Clusters: r.Clusters,
|
||||
Git: r.Git,
|
||||
PullRequest: r.PullRequest,
|
||||
Matrix: matrixGen,
|
||||
Merge: mergeGen,
|
||||
}
|
||||
generators := GetRelevantGenerators(base, m.supportedGenerators)
|
||||
|
||||
@@ -209,6 +200,17 @@ func (m *MergeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.App
|
||||
|
||||
}
|
||||
|
||||
func getMergeGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MergeGenerator, error) {
|
||||
if r.Merge == nil {
|
||||
return nil, nil
|
||||
}
|
||||
merge, err := argoprojiov1alpha1.ToNestedMergeGenerator(r.Merge)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return merge.ToMergeGenerator(), nil
|
||||
}
|
||||
|
||||
// GetTemplate gets the Template field for the MergeGenerator.
|
||||
func (m *MergeGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
|
||||
return &appSetGenerator.Merge.Template
|
||||
|
||||
@@ -265,6 +265,16 @@
|
||||
"description": "the repoURL to restrict returned list applications.",
|
||||
"name": "repo",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "multi",
|
||||
"description": "the project names to restrict returned list applications (legacy name for backwards-compatibility).",
|
||||
"name": "project",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -529,6 +539,16 @@
|
||||
"description": "the repoURL to restrict returned list applications.",
|
||||
"name": "repo",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "multi",
|
||||
"description": "the project names to restrict returned list applications (legacy name for backwards-compatibility).",
|
||||
"name": "project",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -3167,6 +3187,16 @@
|
||||
"description": "the repoURL to restrict returned list applications.",
|
||||
"name": "repo",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "multi",
|
||||
"description": "the project names to restrict returned list applications (legacy name for backwards-compatibility).",
|
||||
"name": "project",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
||||
@@ -207,7 +207,7 @@ var validatorsByGroup = map[string]settingValidator{
|
||||
}
|
||||
ssoProvider = "Dex"
|
||||
} else if general.OIDCConfigRAW != "" {
|
||||
if _, err := settings.UnmarshalOIDCConfig(general.OIDCConfigRAW); err != nil {
|
||||
if err := settings.ValidateOIDCConfig(general.OIDCConfigRAW); err != nil {
|
||||
return "", fmt.Errorf("invalid oidc.config: %v", err)
|
||||
}
|
||||
ssoProvider = "OIDC"
|
||||
|
||||
@@ -8,8 +8,10 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/argoproj/pkg/rand"
|
||||
|
||||
@@ -73,9 +75,8 @@ func runCommand(ctx context.Context, command Command, path string, env []string)
|
||||
}
|
||||
logCtx := log.WithFields(log.Fields{"execID": execId})
|
||||
|
||||
// log in a way we can copy-and-paste into a terminal
|
||||
args := strings.Join(cmd.Args, " ")
|
||||
logCtx.WithFields(log.Fields{"dir": cmd.Dir}).Info(args)
|
||||
argsToLog := getCommandArgsToLog(cmd)
|
||||
logCtx.WithFields(log.Fields{"dir": cmd.Dir}).Info(argsToLog)
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
@@ -106,7 +107,7 @@ func runCommand(ctx context.Context, command Command, path string, env []string)
|
||||
logCtx.WithFields(log.Fields{"duration": duration}).Debug(output)
|
||||
|
||||
if err != nil {
|
||||
err := newCmdError(args, errors.New(err.Error()), strings.TrimSpace(stderr.String()))
|
||||
err := newCmdError(argsToLog, errors.New(err.Error()), strings.TrimSpace(stderr.String()))
|
||||
logCtx.Error(err.Error())
|
||||
return strings.TrimSuffix(output, "\n"), err
|
||||
}
|
||||
@@ -114,6 +115,28 @@ func runCommand(ctx context.Context, command Command, path string, env []string)
|
||||
return strings.TrimSuffix(output, "\n"), nil
|
||||
}
|
||||
|
||||
// getCommandArgsToLog represents the given command in a way that we can copy-and-paste into a terminal
|
||||
func getCommandArgsToLog(cmd *exec.Cmd) string {
|
||||
var argsToLog []string
|
||||
for _, arg := range cmd.Args {
|
||||
containsSpace := false
|
||||
for _, r := range arg {
|
||||
if unicode.IsSpace(r) {
|
||||
containsSpace = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if containsSpace {
|
||||
// add quotes and escape any internal quotes
|
||||
argsToLog = append(argsToLog, strconv.Quote(arg))
|
||||
} else {
|
||||
argsToLog = append(argsToLog, arg)
|
||||
}
|
||||
}
|
||||
args := strings.Join(argsToLog, " ")
|
||||
return args
|
||||
}
|
||||
|
||||
type CmdError struct {
|
||||
Args string
|
||||
Stderr string
|
||||
|
||||
@@ -2,6 +2,7 @@ package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -266,3 +267,30 @@ func TestRunCommandContextTimeout(t *testing.T) {
|
||||
assert.Error(t, err) // The command should time out, causing an error.
|
||||
assert.Less(t, after.Sub(before), 1*time.Second)
|
||||
}
|
||||
|
||||
func Test_getCommandArgsToLog(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no spaces",
|
||||
args: []string{"sh", "-c", "cat"},
|
||||
expected: "sh -c cat",
|
||||
},
|
||||
{
|
||||
name: "spaces",
|
||||
args: []string{"sh", "-c", `echo "hello world"`},
|
||||
expected: `sh -c "echo \"hello world\""`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tcc := tc
|
||||
t.Run(tcc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert.Equal(t, tcc.expected, getCommandArgsToLog(exec.Command(tcc.args[0], tcc.args[1:]...)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Default service addresses and URLS of Argo CD internal services
|
||||
@@ -293,3 +296,10 @@ const (
|
||||
// Keep alive is 2x enforcement minimum to ensure network jitter does not introduce ENHANCE_YOUR_CALM errors
|
||||
GRPCKeepAliveTime = 2 * GRPCKeepAliveEnforcementMinimum
|
||||
)
|
||||
|
||||
// Common error messages
|
||||
const TokenVerificationError = "failed to verify the token"
|
||||
|
||||
var TokenVerificationErr = errors.New(TokenVerificationError)
|
||||
|
||||
var PermissionDeniedAPIError = status.Error(codes.PermissionDenied, "permission denied")
|
||||
|
||||
17
controller/cache/cache.go
vendored
17
controller/cache/cache.go
vendored
@@ -25,6 +25,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/controller/metrics"
|
||||
@@ -389,6 +390,20 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
|
||||
return nil, fmt.Errorf("controller is configured to ignore cluster %s", cluster.Server)
|
||||
}
|
||||
|
||||
clusterCacheConfig := cluster.RESTConfig()
|
||||
// Controller dynamically fetches all resource types available on the cluster
|
||||
// using a discovery API that may contain deprecated APIs.
|
||||
// This causes log flooding when managing a large number of clusters.
|
||||
// https://github.com/argoproj/argo-cd/issues/11973
|
||||
// However, we can safely suppress deprecation warnings
|
||||
// because we do not rely on resources with a particular API group or version.
|
||||
// https://kubernetes.io/blog/2020/09/03/warnings/#customize-client-handling
|
||||
//
|
||||
// Completely suppress warning logs only for log levels that are less than Debug.
|
||||
if log.GetLevel() < log.DebugLevel {
|
||||
clusterCacheConfig.WarningHandler = rest.NoWarnings{}
|
||||
}
|
||||
|
||||
clusterCacheOpts := []clustercache.UpdateSettingsFunc{
|
||||
clustercache.SetListSemaphore(semaphore.NewWeighted(clusterCacheListSemaphoreSize)),
|
||||
clustercache.SetListPageSize(clusterCacheListPageSize),
|
||||
@@ -420,7 +435,7 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
|
||||
clustercache.SetRetryOptions(clusterCacheAttemptLimit, clusterCacheRetryUseBackoff, isRetryableError),
|
||||
}
|
||||
|
||||
clusterCache = clustercache.NewClusterCache(cluster.RESTConfig(), clusterCacheOpts...)
|
||||
clusterCache = clustercache.NewClusterCache(clusterCacheConfig, clusterCacheOpts...)
|
||||
|
||||
_ = clusterCache.OnResourceUpdated(func(newRes *clustercache.Resource, oldRes *clustercache.Resource, namespaceResources map[kube.ResourceKey]*clustercache.Resource) {
|
||||
toNotify := make(map[string]bool)
|
||||
|
||||
@@ -9,16 +9,6 @@ setTimeout(function() {
|
||||
caret.innerHTML = "<i class='fa fa-caret-down dropdown-caret'></i>"
|
||||
caret.classList.add('dropdown-caret')
|
||||
div.querySelector('.rst-current-version').appendChild(caret);
|
||||
div.querySelector('.rst-current-version').addEventListener('click', function() {
|
||||
const classes = container.className.split(' ');
|
||||
const index = classes.indexOf('shift-up');
|
||||
if (index === -1) {
|
||||
classes.push('shift-up');
|
||||
} else {
|
||||
classes.splice(index, 1);
|
||||
}
|
||||
container.className = classes.join(' ');
|
||||
});
|
||||
}
|
||||
|
||||
var CSSLink = document.createElement('link');
|
||||
|
||||
42
docs/faq.md
42
docs/faq.md
@@ -122,9 +122,9 @@ To terminate the sync, click on the "synchronisation" then "terminate":
|
||||
|
||||
 
|
||||
|
||||
## Why Is My App Out Of Sync Even After Syncing?
|
||||
## Why Is My App `Out Of Sync` Even After Syncing?
|
||||
|
||||
Is some cases, the tool you use may conflict with Argo CD by adding the `app.kubernetes.io/instance` label. E.g. using
|
||||
In some cases, the tool you use may conflict with Argo CD by adding the `app.kubernetes.io/instance` label. E.g. using
|
||||
Kustomize common labels feature.
|
||||
|
||||
Argo CD automatically sets the `app.kubernetes.io/instance` label and uses it to determine which resources form the app.
|
||||
@@ -151,7 +151,7 @@ E.g.
|
||||
To fix this use diffing
|
||||
customizations [settings](./user-guide/diffing.md#known-kubernetes-types-in-crds-resource-limits-volume-mounts-etc).
|
||||
|
||||
## How Do I Fix "invalid cookie, longer than max length 4093"?
|
||||
## How Do I Fix `invalid cookie, longer than max length 4093`?
|
||||
|
||||
Argo CD uses a JWT as the auth token. You likely are part of many groups and have gone over the 4KB limit which is set
|
||||
for cookies. You can get the list of groups by opening "developer tools -> network"
|
||||
@@ -218,4 +218,38 @@ resource.customizations.health.bitnami.com_SealedSecret: |
|
||||
hs.status = "Healthy"
|
||||
hs.message = "Controller doesn't report resource status"
|
||||
return hs
|
||||
```
|
||||
```
|
||||
|
||||
## How do I fix `The order in patch list … doesn't match $setElementOrder list: …`?
|
||||
|
||||
An application may trigger a sync error labeled a `ComparisonError` with a message like:
|
||||
|
||||
> The order in patch list: [map[name:**KEY_BC** value:150] map[name:**KEY_BC** value:500] map[name:**KEY_BD** value:250] map[name:**KEY_BD** value:500] map[name:KEY_BI value:something]] doesn't match $setElementOrder list: [map[name:KEY_AA] map[name:KEY_AB] map[name:KEY_AC] map[name:KEY_AD] map[name:KEY_AE] map[name:KEY_AF] map[name:KEY_AG] map[name:KEY_AH] map[name:KEY_AI] map[name:KEY_AJ] map[name:KEY_AK] map[name:KEY_AL] map[name:KEY_AM] map[name:KEY_AN] map[name:KEY_AO] map[name:KEY_AP] map[name:KEY_AQ] map[name:KEY_AR] map[name:KEY_AS] map[name:KEY_AT] map[name:KEY_AU] map[name:KEY_AV] map[name:KEY_AW] map[name:KEY_AX] map[name:KEY_AY] map[name:KEY_AZ] map[name:KEY_BA] map[name:KEY_BB] map[name:**KEY_BC**] map[name:**KEY_BD**] map[name:KEY_BE] map[name:KEY_BF] map[name:KEY_BG] map[name:KEY_BH] map[name:KEY_BI] map[name:**KEY_BC**] map[name:**KEY_BD**]]
|
||||
|
||||
|
||||
There are two parts to the message:
|
||||
|
||||
1. `The order in patch list: [`
|
||||
|
||||
This identifies values for items, especially items that appear multiple times:
|
||||
|
||||
> map[name:**KEY_BC** value:150] map[name:**KEY_BC** value:500] map[name:**KEY_BD** value:250] map[name:**KEY_BD** value:500] map[name:KEY_BI value:something]
|
||||
|
||||
You'll want to identify the keys that are duplicated -- you can focus on the first part, as each duplicated key will appear, once for each of its value with its value in the first list. The second list is really just
|
||||
|
||||
`]`
|
||||
|
||||
2. `doesn't match $setElementOrder list: [`
|
||||
|
||||
This includes all of the keys. It's included for debugging purposes -- you don't need to pay much attention to it. It will give you a hint about the precise location in the list for the duplicated keys:
|
||||
|
||||
> map[name:KEY_AA] map[name:KEY_AB] map[name:KEY_AC] map[name:KEY_AD] map[name:KEY_AE] map[name:KEY_AF] map[name:KEY_AG] map[name:KEY_AH] map[name:KEY_AI] map[name:KEY_AJ] map[name:KEY_AK] map[name:KEY_AL] map[name:KEY_AM] map[name:KEY_AN] map[name:KEY_AO] map[name:KEY_AP] map[name:KEY_AQ] map[name:KEY_AR] map[name:KEY_AS] map[name:KEY_AT] map[name:KEY_AU] map[name:KEY_AV] map[name:KEY_AW] map[name:KEY_AX] map[name:KEY_AY] map[name:KEY_AZ] map[name:KEY_BA] map[name:KEY_BB] map[name:**KEY_BC**] map[name:**KEY_BD**] map[name:KEY_BE] map[name:KEY_BF] map[name:KEY_BG] map[name:KEY_BH] map[name:KEY_BI] map[name:**KEY_BC**] map[name:**KEY_BD**]
|
||||
|
||||
`]`
|
||||
|
||||
In this case, the duplicated keys have been **emphasized** to help you identify the problematic keys. Many editors have the ability to highlight all instances of a string, using such an editor can help with such problems.
|
||||
|
||||
The most common instance of this error is with `env:` fields for `containers`.
|
||||
|
||||
!!! note "Dynamic applications"
|
||||
It's possible that your application is being generated by a tool in which case the duplication might not be evident within the scope of a single file. If you have trouble debugging this problem, consider filing a ticket to the owner of the generator tool asking them to improve its validation and error reporting.
|
||||
|
||||
@@ -125,7 +125,10 @@ spec:
|
||||
|
||||
# Destination cluster and namespace to deploy the application
|
||||
destination:
|
||||
# cluster API URL
|
||||
server: https://kubernetes.default.svc
|
||||
# or cluster name
|
||||
# name: in-cluster
|
||||
# The namespace will only be set for namespace-scoped resources that have not set a value for .metadata.namespace
|
||||
namespace: guestbook
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ spec:
|
||||
* `repo`: Required name of the GitHub repository.
|
||||
* `api`: If using GitHub Enterprise, the URL to access it. (Optional)
|
||||
* `tokenRef`: A `Secret` name and key containing the GitHub access token to use for requests. If not specified, will make anonymous requests which have a lower rate limit and can only see public repositories. (Optional)
|
||||
* `labels`: Labels is used to filter the PRs that you want to target. (Optional)
|
||||
* `labels`: Filter the PRs to those containing **all** of the labels listed. (Optional)
|
||||
|
||||
## Gitea
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ data:
|
||||
help.download.windows-amd64: "path-or-url-to-download"
|
||||
|
||||
# A dex connector configuration (optional). See SSO configuration documentation:
|
||||
# https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/sso
|
||||
# https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/user-management/index.md#sso
|
||||
# https://dexidp.io/docs/connectors/
|
||||
dex.config: |
|
||||
connectors:
|
||||
|
||||
@@ -51,7 +51,7 @@ following example builds an entirely customized repo-server from a Dockerfile, i
|
||||
dependencies that may be needed for generating manifests.
|
||||
|
||||
```Dockerfile
|
||||
FROM argoproj/argocd:latest
|
||||
FROM argoproj/argocd:v2.5.4 # Replace tag with the appropriate argo version
|
||||
|
||||
# Switch to root for the ability to perform install
|
||||
USER root
|
||||
|
||||
@@ -77,7 +77,7 @@ spec:
|
||||
```
|
||||
|
||||
!!! warning
|
||||
By default, deleting an application will not perform a cascade delete, which would delete its resources. You must add the finalizer if you want this behaviour - which you may well not want.
|
||||
Without the `resources-finalizer.argocd.argoproj.io` finalizer, deleting an application will not delete the resources it manages. To perform a cascading delete, you must add the finalizer. See [App Deletion](../user-guide/app_deletion.md#about-the-deletion-finalizer).
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
|
||||
@@ -5,7 +5,7 @@ Argo CD provides built-in health assessment for several standard Kubernetes type
|
||||
surfaced to the overall Application health status as a whole. The following checks are made for
|
||||
specific types of kubernetes resources:
|
||||
|
||||
### Deployment, ReplicaSet, StatefulSet DaemonSet
|
||||
### Deployment, ReplicaSet, StatefulSet, DaemonSet
|
||||
* Observed generation is equal to desired generation.
|
||||
* Number of **updated** replicas equals the number of desired replicas.
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
# High Availability
|
||||
|
||||
Argo CD is largely stateless, all data is persisted as Kubernetes objects, which in turn is stored in Kubernetes' etcd. Redis is only used as a throw-away cache and can be lost. When lost, it will be rebuilt without loss of service.
|
||||
Argo CD is largely stateless. All data is persisted as Kubernetes objects, which in turn is stored in Kubernetes' etcd. Redis is only used as a throw-away cache and can be lost. When lost, it will be rebuilt without loss of service.
|
||||
|
||||
A set of HA manifests are provided for users who wish to run Argo CD in a highly available manner. This runs more containers, and runs Redis in HA mode.
|
||||
|
||||
[Manifests ⧉](https://github.com/argoproj/argo-cd/tree/master/manifests)
|
||||
A set of [HA manifests](https://github.com/argoproj/argo-cd/tree/master/manifests/ha) are provided for users who wish to run Argo CD in a highly available manner. This runs more containers, and runs Redis in HA mode.
|
||||
|
||||
!!! note
|
||||
The HA installation will require at least three different nodes due to pod anti-affinity roles in the specs.
|
||||
@@ -17,11 +15,11 @@ A set of HA manifests are provided for users who wish to run Argo CD in a highly
|
||||
|
||||
The `argocd-repo-server` is responsible for cloning Git repository, keeping it up to date and generating manifests using the appropriate tool.
|
||||
|
||||
* `argocd-repo-server` fork/exec config management tool to generate manifests. The fork can fail due to lack of memory and limit on the number of OS threads.
|
||||
The `--parallelismlimit` flag controls how many manifests generations are running concurrently and allows avoiding OOM kills.
|
||||
* `argocd-repo-server` fork/exec config management tool to generate manifests. The fork can fail due to lack of memory or limit on the number of OS threads.
|
||||
The `--parallelismlimit` flag controls how many manifests generations are running concurrently and helps avoid OOM kills.
|
||||
|
||||
* the `argocd-repo-server` ensures that repository is in the clean state during the manifest generation using config management tools such as Kustomize, Helm
|
||||
or custom plugin. As a result Git repositories with multiple applications might be affect repository server performance.
|
||||
or custom plugin. As a result Git repositories with multiple applications might affect repository server performance.
|
||||
Read [Monorepo Scaling Considerations](#monorepo-scaling-considerations) for more information.
|
||||
|
||||
* `argocd-repo-server` clones repository into `/tmp` ( of path specified in `TMPDIR` env variable ). Pod might run out of disk space if have too many repository
|
||||
@@ -30,7 +28,7 @@ or repositories has a lot of files. To avoid this problem mount persistent volum
|
||||
* `argocd-repo-server` `git ls-remote` to resolve ambiguous revision such as `HEAD`, branch or tag name. This operation is happening pretty frequently
|
||||
and might fail. To avoid failed syncs use `ARGOCD_GIT_ATTEMPTS_COUNT` environment variable to retry failed requests.
|
||||
|
||||
* `argocd-repo-server` Every 3m (by default) Argo CD checks for changes to the app manifests. Argo CD assumes by default that manifests only change when the repo changes, so it caches generated manifests (for 24h by default). With Kustomize remote bases, or Helm patch releases, the manifests can change even though the repo has not changed. By reducing the cache time, you can get the changes without waiting for 24h. Use `--repo-cache-expiration duration`, and we'd suggest in low volume environments you try '1h'. Bear in mind this will negate the benefit of caching if set too low.
|
||||
* `argocd-repo-server` Every 3m (by default) Argo CD checks for changes to the app manifests. Argo CD assumes by default that manifests only change when the repo changes, so it caches the generated manifests (for 24h by default). With Kustomize remote bases, or Helm patch releases, the manifests can change even though the repo has not changed. By reducing the cache time, you can get the changes without waiting for 24h. Use `--repo-cache-expiration duration`, and we'd suggest in low volume environments you try '1h'. Bear in mind that this will negate the benefits of caching if set too low.
|
||||
|
||||
* `argocd-repo-server` fork exec config management tools such as `helm` or `kustomize` and enforces 90 seconds timeout. The timeout can be increased using `ARGOCD_EXEC_TIMEOUT` env variable. The value should be in Go time duration string format, for example, `2m30s`.
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ kind: Kustomization
|
||||
|
||||
namespace: argocd
|
||||
resources:
|
||||
- https://raw.githubusercontent.com/argoproj/argo-cd/v2.0.4/manifests/ha/install.yaml
|
||||
- github.com/argoproj/argo-cd/manifests/ha?ref=v2.6.2
|
||||
```
|
||||
|
||||
## Helm
|
||||
|
||||
@@ -15,9 +15,11 @@ spec:
|
||||
- '*'
|
||||
|
||||
# Only permit applications to deploy to the guestbook namespace in the same cluster
|
||||
# Destination clusters can be identified by 'server', 'name', or both.
|
||||
destinations:
|
||||
- namespace: guestbook
|
||||
server: https://kubernetes.default.svc
|
||||
name: in-cluster
|
||||
|
||||
# Deny all cluster-scoped resources from being created, except for Namespace
|
||||
clusterResourceWhitelist:
|
||||
|
||||
@@ -9,9 +9,8 @@ Operators can add actions to custom resources in form of a Lua script and expand
|
||||
|
||||
Argo CD supports custom resource actions written in [Lua](https://www.lua.org/). This is useful if you:
|
||||
|
||||
* Have a custom resource for which Argo CD does not provide any built-in actions.
|
||||
* Have a commonly performed manual task that might be error prone if executed by users via `kubectl`
|
||||
|
||||
* Have a custom resource for which Argo CD does not provide any built-in actions.
|
||||
* Have a commonly performed manual task that might be error prone if executed by users via `kubectl`
|
||||
|
||||
You can define your own custom resource actions in the `argocd-cm` ConfigMap.
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
# Secret Management
|
||||
|
||||
Argo CD is un-opinionated about how secrets are managed. There's many ways to do it and there's no one-size-fits-all solution. Here's some ways people are doing GitOps secrets:
|
||||
Argo CD is un-opinionated about how secrets are managed. There are many ways to do it, and there's no one-size-fits-all solution.
|
||||
|
||||
Many solutions use plugins to inject secrets into the application manifests. See [Mitigating Risks of Secret-Injection Plugins](#mitigating-risks-of-secret-injection-plugins)
|
||||
below to make sure you use those plugins securely.
|
||||
|
||||
Here are some ways people are doing GitOps secrets:
|
||||
|
||||
* [Bitnami Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets)
|
||||
* [GoDaddy Kubernetes External Secrets](https://github.com/godaddy/kubernetes-external-secrets)
|
||||
@@ -15,3 +20,17 @@ Argo CD is un-opinionated about how secrets are managed. There's many ways to do
|
||||
* [argocd-vault-replacer](https://github.com/crumbhole/argocd-vault-replacer)
|
||||
|
||||
For discussion, see [#1364](https://github.com/argoproj/argo-cd/issues/1364)
|
||||
|
||||
## Mitigating Risks of Secret-Injection Plugins
|
||||
|
||||
Argo CD caches the manifests generated by plugins, along with the injected secrets, in its Redis instance. Those
|
||||
manifests are also available via the repo-server API (a gRPC service). This means that the secrets are available to
|
||||
anyone who has access to the Redis instance or to the repo-server.
|
||||
|
||||
Consider these steps to mitigate the risks of secret-injection plugins:
|
||||
|
||||
1. Set up network policies to prevent direct access to Argo CD components (Redis and the repo-server). Make sure your
|
||||
cluster supports those network policies and can actually enforce them.
|
||||
2. Consider running Argo CD on its own cluster, with no other applications running on it.
|
||||
3. [Enable password authentication on the Redis instance](https://github.com/argoproj/argo-cd/issues/3130) (currently
|
||||
only supported for non-HA Argo CD installations).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# v1.8 to v2.0
|
||||
# v1.8 to 2.0
|
||||
|
||||
## Redis Upgraded to v6.2.1
|
||||
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# v2.3 to 2.4
|
||||
|
||||
## Known Issues
|
||||
|
||||
### Broken `project` filter before 2.4.27
|
||||
|
||||
Argo CD 2.4.0 introduced a breaking API change, renaming the `project` filter to `projects`.
|
||||
|
||||
#### Impact to API clients
|
||||
|
||||
A similar issue applies to other API clients which communicate with the Argo CD API server via its REST API. If the
|
||||
client uses the `project` field to filter projects, the filter will not be applied. **The failing project filter could
|
||||
have detrimental consequences if, for example, you rely on it to list Applications to be deleted.**
|
||||
|
||||
#### Impact to CLI clients
|
||||
|
||||
CLI clients older that v2.4.0 rely on client-side filtering and are not impacted by this bug.
|
||||
|
||||
#### How to fix the problem
|
||||
|
||||
Upgrade to Argo CD >=2.4.27, >=2.5.15, or >=2.6.6. This version of Argo CD will accept both `project` and `projects` as
|
||||
valid filters.
|
||||
|
||||
## KSonnet support is removed
|
||||
|
||||
Ksonnet was deprecated in [2019](https://github.com/ksonnet/ksonnet/pull/914/files) and is no longer maintained.
|
||||
|
||||
@@ -37,7 +37,6 @@ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/<v
|
||||
|
||||
<hr/>
|
||||
|
||||
* [v2.4 to v2.5](./2.4-2.5.md)
|
||||
* [v2.3 to v2.4](./2.3-2.4.md)
|
||||
* [v2.2 to v2.3](./2.2-2.3.md)
|
||||
* [v2.1 to v2.2](./2.1-2.2.md)
|
||||
|
||||
@@ -301,6 +301,19 @@ data:
|
||||
issuer: https://dev-123456.oktapreview.com
|
||||
clientID: aaaabbbbccccddddeee
|
||||
clientSecret: $oidc.okta.clientSecret
|
||||
|
||||
# Optional list of allowed aud claims. If omitted or empty, defaults to the clientID value above (and the
|
||||
# cliCientID, if that is also specified). If you specify a list and want the clientID to be allowed, you must
|
||||
# explicitly include it in the list.
|
||||
# Token verification will pass if any of the token's audiences matches any of the audiences in this list.
|
||||
allowedAudiences:
|
||||
- aaaabbbbccccddddeee
|
||||
- qqqqwwwweeeerrrrttt
|
||||
|
||||
# Optional. If false, tokens without an audience will always fail validation. If true, tokens without an audience
|
||||
# will always pass validation.
|
||||
# Defaults to true for Argo CD < 2.6.0. Defaults to false for Argo CD >= 2.6.0.
|
||||
skipAudienceCheckWhenTokenHasNoAudience: true
|
||||
|
||||
# Optional set of OIDC scopes to request. If omitted, defaults to: ["openid", "profile", "email", "groups"]
|
||||
requestedScopes: ["openid", "profile", "email", "groups"]
|
||||
|
||||
@@ -12,5 +12,6 @@ Before effectively using Argo CD, it is necessary to understand the underlying t
|
||||
* Depending on how you plan to template your applications:
|
||||
* [Kustomize](https://kustomize.io)
|
||||
* [Helm](https://helm.sh)
|
||||
* If you're integrating with Jenkins:
|
||||
* [Jenkins User Guide](https://jenkins.io)
|
||||
* If you're integrating with a CI tool:
|
||||
* [GitHub Actions Documentation](https://docs.github.com/en/actions)
|
||||
* [Jenkins User Guide](https://www.jenkins.io/doc/book/)
|
||||
|
||||
@@ -24,9 +24,10 @@ argocd app delete APPNAME
|
||||
|
||||
# Deletion Using `kubectl`
|
||||
|
||||
To perform a non-cascade delete:
|
||||
To perform a non-cascade delete, make sure the finalizer is unset and then delete the app:
|
||||
|
||||
```bash
|
||||
kubectl patch app APPNAME -p '{"metadata": {"finalizers": null}}' --type merge
|
||||
kubectl delete app APPNAME
|
||||
```
|
||||
|
||||
|
||||
@@ -43,6 +43,9 @@ spec:
|
||||
recurse: true
|
||||
```
|
||||
|
||||
!!! warning
|
||||
Directory-type applications only work for plain manifest files. If Argo CD encounters Kustomize, Helm, or Jsonnet files when directory: is set, it will fail to render the manifests.
|
||||
|
||||
## Including/Excluding Files
|
||||
|
||||
### Including Only Certain Files
|
||||
|
||||
@@ -283,7 +283,7 @@ Helm, [starting with v3.6.1](https://github.com/helm/helm/releases/tag/v3.6.1),
|
||||
prevents sending repository credentials to download charts that are being served
|
||||
from a different domain than the repository.
|
||||
|
||||
If needed, it is possible to specifically set the Helm version to template with by setting the `helm-pass-credentials` flag on the cli:
|
||||
If needed, it is possible to opt into passing credentials for all domains by setting the `helm-pass-credentials` flag on the cli:
|
||||
|
||||
```bash
|
||||
argocd app set helm-guestbook --helm-pass-credentials
|
||||
|
||||
@@ -69,7 +69,7 @@ spec:
|
||||
source:
|
||||
repoURL: https://github.com/argoproj/argocd-example-apps.git
|
||||
targetRevision: HEAD
|
||||
path: guestbook-kustomize
|
||||
path: kustomize-guestbook
|
||||
|
||||
kustomize:
|
||||
version: v3.5.4
|
||||
|
||||
23
go.mod
23
go.mod
@@ -61,7 +61,7 @@ require (
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pquerna/cachecontrol v0.1.0 // indirect
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/prometheus/client_golang v1.11.1
|
||||
github.com/r3labs/diff v1.1.0
|
||||
github.com/robfig/cron v1.2.0
|
||||
github.com/rs/cors v1.8.0 // indirect
|
||||
@@ -76,10 +76,10 @@ require (
|
||||
github.com/xanzy/go-gitlab v0.60.0
|
||||
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
|
||||
golang.org/x/net v0.0.0-20220621193019-9d032be2e588
|
||||
golang.org/x/net v0.7.0
|
||||
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
|
||||
golang.org/x/term v0.5.0
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa
|
||||
google.golang.org/grpc v1.45.0
|
||||
google.golang.org/protobuf v1.28.0
|
||||
@@ -201,11 +201,11 @@ require (
|
||||
go.mongodb.org/mongo-driver v1.1.2 // indirect
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||
golang.org/x/exp v0.0.0-20210901193431-a062eea981d2 // indirect
|
||||
golang.org/x/mod v0.5.1-0.20210830214625-1b1db11ec8f4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gomodules.xyz/envconfig v1.3.1-0.20190308184047-426f31af0d45 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
@@ -214,7 +214,7 @@ require (
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
k8s.io/apiserver v0.23.1
|
||||
@@ -249,6 +249,8 @@ require (
|
||||
)
|
||||
|
||||
replace (
|
||||
// Address CVE-2021-4238
|
||||
github.com/Masterminds/goutils => github.com/Masterminds/goutils v1.1.1
|
||||
// https://github.com/golang/go/issues/33546#issuecomment-519656923
|
||||
github.com/go-check/check => github.com/go-check/check v0.0.0-20180628173108-788fd7840127
|
||||
|
||||
@@ -257,6 +259,9 @@ replace (
|
||||
github.com/grpc-ecosystem/grpc-gateway => github.com/grpc-ecosystem/grpc-gateway v1.16.0
|
||||
github.com/improbable-eng/grpc-web => github.com/improbable-eng/grpc-web v0.0.0-20181111100011-16092bd1d58a
|
||||
|
||||
// Avoid CVE-2022-3064
|
||||
gopkg.in/yaml.v2 => gopkg.in/yaml.v2 v2.2.4
|
||||
|
||||
// Avoid CVE-2022-28948
|
||||
gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
|
||||
|
||||
|
||||
40
go.sum
40
go.sum
@@ -87,8 +87,8 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU=
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
|
||||
github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
|
||||
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
@@ -954,8 +954,9 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
|
||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s=
|
||||
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@@ -1282,8 +1283,9 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.5.1-0.20210830214625-1b1db11ec8f4 h1:7Qds88gNaRx0Dz/1wOwXlR7asekh1B1u26wEwN6FcEI=
|
||||
golang.org/x/mod v0.5.1-0.20210830214625-1b1db11ec8f4/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1346,8 +1348,8 @@ golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220621193019-9d032be2e588 h1:9ubFuySsnAJYGyJrZ3koiEv8FyqofCBdz3G9Mbf2YFc=
|
||||
golang.org/x/net v0.0.0-20220621193019-9d032be2e588/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1380,8 +1382,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1491,12 +1494,13 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1505,8 +1509,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1588,8 +1593,9 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff h1:VX/uD7MK0AHXGiScH3fsieUQUcpmRERPDYtqZdJnA+Q=
|
||||
golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1804,16 +1810,8 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
|
||||
gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
|
||||
@@ -35,7 +35,7 @@ spec:
|
||||
runAsNonRoot: true
|
||||
containers:
|
||||
- name: dex
|
||||
image: ghcr.io/dexidp/dex:v2.35.3
|
||||
image: ghcr.io/dexidp/dex:v2.36.0
|
||||
imagePullPolicy: Always
|
||||
command: [/shared/argocd-dex, rundex]
|
||||
securityContext:
|
||||
|
||||
@@ -5,7 +5,7 @@ kind: Kustomization
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.4.18
|
||||
newTag: v2.4.28
|
||||
resources:
|
||||
- ./application-controller
|
||||
- ./dex
|
||||
|
||||
@@ -21,7 +21,7 @@ spec:
|
||||
serviceAccountName: argocd-redis
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7.0.4-alpine
|
||||
image: redis:7.0.8-alpine
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- "--save"
|
||||
|
||||
@@ -9384,7 +9384,7 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -9464,7 +9464,7 @@ spec:
|
||||
- ""
|
||||
- --appendonly
|
||||
- "no"
|
||||
image: redis:7.0.4-alpine
|
||||
image: redis:7.0.8-alpine
|
||||
imagePullPolicy: Always
|
||||
name: redis
|
||||
ports:
|
||||
@@ -9614,7 +9614,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -9663,7 +9663,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -9850,7 +9850,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
|
||||
@@ -12,4 +12,4 @@ resources:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.4.18
|
||||
newTag: v2.4.28
|
||||
|
||||
@@ -11,7 +11,7 @@ patchesStrategicMerge:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.4.18
|
||||
newTag: v2.4.28
|
||||
resources:
|
||||
- ../../base/application-controller
|
||||
- ../../base/applicationset-controller
|
||||
|
||||
@@ -770,7 +770,7 @@ spec:
|
||||
topologyKey: kubernetes.io/hostname
|
||||
initContainers:
|
||||
- name: config-init
|
||||
image: haproxy:2.0.29-alpine
|
||||
image: haproxy:2.0.31-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
{}
|
||||
@@ -790,7 +790,7 @@ spec:
|
||||
runAsUser: 1000
|
||||
containers:
|
||||
- name: haproxy
|
||||
image: haproxy:2.0.29-alpine
|
||||
image: haproxy:2.0.31-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -878,7 +878,7 @@ spec:
|
||||
automountServiceAccountToken: false
|
||||
initContainers:
|
||||
- name: config-init
|
||||
image: redis:7.0.4-alpine
|
||||
image: redis:7.0.8-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
{}
|
||||
@@ -906,7 +906,7 @@ spec:
|
||||
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7.0.4-alpine
|
||||
image: redis:7.0.8-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- redis-server
|
||||
@@ -947,7 +947,7 @@ spec:
|
||||
lifecycle:
|
||||
{}
|
||||
- name: sentinel
|
||||
image: redis:7.0.4-alpine
|
||||
image: redis:7.0.8-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- redis-sentinel
|
||||
|
||||
@@ -9,12 +9,12 @@ redis-ha:
|
||||
haproxy:
|
||||
enabled: true
|
||||
image:
|
||||
tag: 2.0.29-alpine
|
||||
tag: 2.0.31-alpine
|
||||
timeout:
|
||||
server: 6m
|
||||
client: 6m
|
||||
checkInterval: 3s
|
||||
image:
|
||||
tag: 7.0.4-alpine
|
||||
tag: 7.0.8-alpine
|
||||
sentinel:
|
||||
bind: "0.0.0.0"
|
||||
|
||||
@@ -10319,7 +10319,7 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -10391,7 +10391,7 @@ spec:
|
||||
- command:
|
||||
- /shared/argocd-dex
|
||||
- rundex
|
||||
image: ghcr.io/dexidp/dex:v2.35.3
|
||||
image: ghcr.io/dexidp/dex:v2.36.0
|
||||
imagePullPolicy: Always
|
||||
name: dex
|
||||
ports:
|
||||
@@ -10416,7 +10416,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -10456,7 +10456,7 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -10525,7 +10525,7 @@ spec:
|
||||
app.kubernetes.io/name: argocd-redis-ha-haproxy
|
||||
topologyKey: kubernetes.io/hostname
|
||||
containers:
|
||||
- image: haproxy:2.0.29-alpine
|
||||
- image: haproxy:2.0.31-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle: {}
|
||||
livenessProbe:
|
||||
@@ -10554,7 +10554,7 @@ spec:
|
||||
- /readonly/haproxy_init.sh
|
||||
command:
|
||||
- sh
|
||||
image: haproxy:2.0.29-alpine
|
||||
image: haproxy:2.0.31-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: config-init
|
||||
volumeMounts:
|
||||
@@ -10713,7 +10713,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -10762,7 +10762,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -11009,7 +11009,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -11217,7 +11217,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -11298,7 +11298,7 @@ spec:
|
||||
- /data/conf/redis.conf
|
||||
command:
|
||||
- redis-server
|
||||
image: redis:7.0.4-alpine
|
||||
image: redis:7.0.8-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle: {}
|
||||
livenessProbe:
|
||||
@@ -11336,7 +11336,7 @@ spec:
|
||||
- /data/conf/sentinel.conf
|
||||
command:
|
||||
- redis-sentinel
|
||||
image: redis:7.0.4-alpine
|
||||
image: redis:7.0.8-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle: {}
|
||||
livenessProbe:
|
||||
@@ -11382,7 +11382,7 @@ spec:
|
||||
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
|
||||
- name: SENTINEL_ID_2
|
||||
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
|
||||
image: redis:7.0.4-alpine
|
||||
image: redis:7.0.8-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: config-init
|
||||
volumeMounts:
|
||||
|
||||
@@ -1244,7 +1244,7 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -1316,7 +1316,7 @@ spec:
|
||||
- command:
|
||||
- /shared/argocd-dex
|
||||
- rundex
|
||||
image: ghcr.io/dexidp/dex:v2.35.3
|
||||
image: ghcr.io/dexidp/dex:v2.36.0
|
||||
imagePullPolicy: Always
|
||||
name: dex
|
||||
ports:
|
||||
@@ -1341,7 +1341,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -1381,7 +1381,7 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -1450,7 +1450,7 @@ spec:
|
||||
app.kubernetes.io/name: argocd-redis-ha-haproxy
|
||||
topologyKey: kubernetes.io/hostname
|
||||
containers:
|
||||
- image: haproxy:2.0.29-alpine
|
||||
- image: haproxy:2.0.31-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle: {}
|
||||
livenessProbe:
|
||||
@@ -1479,7 +1479,7 @@ spec:
|
||||
- /readonly/haproxy_init.sh
|
||||
command:
|
||||
- sh
|
||||
image: haproxy:2.0.29-alpine
|
||||
image: haproxy:2.0.31-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: config-init
|
||||
volumeMounts:
|
||||
@@ -1638,7 +1638,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1687,7 +1687,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -1934,7 +1934,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2142,7 +2142,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2223,7 +2223,7 @@ spec:
|
||||
- /data/conf/redis.conf
|
||||
command:
|
||||
- redis-server
|
||||
image: redis:7.0.4-alpine
|
||||
image: redis:7.0.8-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle: {}
|
||||
livenessProbe:
|
||||
@@ -2261,7 +2261,7 @@ spec:
|
||||
- /data/conf/sentinel.conf
|
||||
command:
|
||||
- redis-sentinel
|
||||
image: redis:7.0.4-alpine
|
||||
image: redis:7.0.8-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle: {}
|
||||
livenessProbe:
|
||||
@@ -2307,7 +2307,7 @@ spec:
|
||||
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
|
||||
- name: SENTINEL_ID_2
|
||||
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
|
||||
image: redis:7.0.4-alpine
|
||||
image: redis:7.0.8-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: config-init
|
||||
volumeMounts:
|
||||
|
||||
@@ -9691,7 +9691,7 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -9763,7 +9763,7 @@ spec:
|
||||
- command:
|
||||
- /shared/argocd-dex
|
||||
- rundex
|
||||
image: ghcr.io/dexidp/dex:v2.35.3
|
||||
image: ghcr.io/dexidp/dex:v2.36.0
|
||||
imagePullPolicy: Always
|
||||
name: dex
|
||||
ports:
|
||||
@@ -9788,7 +9788,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -9828,7 +9828,7 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -9903,7 +9903,7 @@ spec:
|
||||
- ""
|
||||
- --appendonly
|
||||
- "no"
|
||||
image: redis:7.0.4-alpine
|
||||
image: redis:7.0.8-alpine
|
||||
imagePullPolicy: Always
|
||||
name: redis
|
||||
ports:
|
||||
@@ -10053,7 +10053,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -10102,7 +10102,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -10345,7 +10345,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -10547,7 +10547,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
|
||||
@@ -616,7 +616,7 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -688,7 +688,7 @@ spec:
|
||||
- command:
|
||||
- /shared/argocd-dex
|
||||
- rundex
|
||||
image: ghcr.io/dexidp/dex:v2.35.3
|
||||
image: ghcr.io/dexidp/dex:v2.36.0
|
||||
imagePullPolicy: Always
|
||||
name: dex
|
||||
ports:
|
||||
@@ -713,7 +713,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -753,7 +753,7 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -828,7 +828,7 @@ spec:
|
||||
- ""
|
||||
- --appendonly
|
||||
- "no"
|
||||
image: redis:7.0.4-alpine
|
||||
image: redis:7.0.8-alpine
|
||||
imagePullPolicy: Always
|
||||
name: redis
|
||||
ports:
|
||||
@@ -978,7 +978,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1027,7 +1027,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -1270,7 +1270,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -1472,7 +1472,7 @@ spec:
|
||||
key: otlp.address
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.4.18
|
||||
image: quay.io/argoproj/argocd:v2.4.28
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
|
||||
@@ -49,7 +49,9 @@ type ApplicationQuery struct {
|
||||
// the selector to restrict returned list to applications only with matched labels
|
||||
Selector *string `protobuf:"bytes,5,opt,name=selector" json:"selector,omitempty"`
|
||||
// the repoURL to restrict returned list applications
|
||||
Repo *string `protobuf:"bytes,6,opt,name=repo" json:"repo,omitempty"`
|
||||
Repo *string `protobuf:"bytes,6,opt,name=repo" json:"repo,omitempty"`
|
||||
// the project names to restrict returned list applications (legacy name for backwards-compatibility)
|
||||
Project []string `protobuf:"bytes,8,rep,name=project" json:"project,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
@@ -130,6 +132,13 @@ func (m *ApplicationQuery) GetRepo() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *ApplicationQuery) GetProject() []string {
|
||||
if m != nil {
|
||||
return m.Project
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NodeQuery struct {
|
||||
// the application's name
|
||||
Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
@@ -2100,147 +2109,148 @@ func init() {
|
||||
}
|
||||
|
||||
var fileDescriptor_df6e82b174b5eaec = []byte{
|
||||
// 2236 bytes of a gzipped FileDescriptorProto
|
||||
// 2243 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x5a, 0xcd, 0x8f, 0x1b, 0x49,
|
||||
0x15, 0x57, 0xcd, 0xa7, 0xfd, 0x9c, 0xcf, 0xda, 0x4d, 0xe8, 0xed, 0x4c, 0x66, 0x47, 0x95, 0xaf,
|
||||
0xc9, 0x24, 0x63, 0x27, 0x26, 0x42, 0xd9, 0x59, 0x10, 0x64, 0x77, 0xc3, 0x6c, 0x96, 0x99, 0xd9,
|
||||
0xd0, 0x93, 0x10, 0xb4, 0x1c, 0xa0, 0xb6, 0xbb, 0xec, 0x69, 0xc6, 0xee, 0xea, 0x74, 0xb7, 0x1d,
|
||||
0x59, 0x21, 0x97, 0x45, 0xdc, 0x10, 0x48, 0xb0, 0x07, 0x84, 0x10, 0x42, 0xac, 0x56, 0xe2, 0x06,
|
||||
0x5c, 0x56, 0x48, 0x5c, 0xe0, 0xc2, 0x87, 0xc4, 0x01, 0xc1, 0x3f, 0x00, 0x11, 0x27, 0x2e, 0x5c,
|
||||
0x39, 0xa2, 0xaa, 0xae, 0x6a, 0x57, 0x7b, 0xec, 0xb6, 0xc3, 0x78, 0xb5, 0xb9, 0xf5, 0x2b, 0x57,
|
||||
0xbd, 0xf7, 0xab, 0x57, 0xbf, 0x7a, 0xaf, 0xde, 0x93, 0xe1, 0x7c, 0xcc, 0xa2, 0x2e, 0x8b, 0x6a,
|
||||
0x34, 0x0c, 0x5b, 0xbe, 0x4b, 0x13, 0x9f, 0x07, 0xe6, 0x77, 0x35, 0x8c, 0x78, 0xc2, 0x71, 0xc5,
|
||||
0x18, 0xb2, 0x97, 0x9a, 0x9c, 0x37, 0x5b, 0xac, 0x46, 0x43, 0xbf, 0x46, 0x83, 0x80, 0x27, 0x72,
|
||||
0x38, 0x4e, 0xa7, 0xda, 0x64, 0xff, 0x66, 0x5c, 0xf5, 0xb9, 0xfc, 0xd5, 0xe5, 0x11, 0xab, 0x75,
|
||||
0xaf, 0xd7, 0x9a, 0x2c, 0x60, 0x11, 0x4d, 0x98, 0xa7, 0xe6, 0xdc, 0xe8, 0xcf, 0x69, 0x53, 0x77,
|
||||
0xcf, 0x0f, 0x58, 0xd4, 0xab, 0x85, 0xfb, 0x4d, 0x31, 0x10, 0xd7, 0xda, 0x2c, 0xa1, 0xc3, 0x56,
|
||||
0x6d, 0x35, 0xfd, 0x64, 0xaf, 0xf3, 0x6e, 0xd5, 0xe5, 0xed, 0x1a, 0x8d, 0x9a, 0x3c, 0x8c, 0xf8,
|
||||
0x37, 0xe5, 0xc7, 0xba, 0xeb, 0xd5, 0xba, 0xf5, 0xbe, 0x02, 0x73, 0x2f, 0xdd, 0xeb, 0xb4, 0x15,
|
||||
0xee, 0xd1, 0x83, 0xda, 0x6e, 0x8f, 0xd1, 0x16, 0xb1, 0x90, 0x2b, 0xdf, 0xc8, 0x4f, 0x3f, 0xe1,
|
||||
0x51, 0xcf, 0xf8, 0x4c, 0xd5, 0x90, 0x8f, 0x10, 0x9c, 0xb8, 0xd5, 0xb7, 0xf7, 0xe5, 0x0e, 0x8b,
|
||||
0x7a, 0x18, 0xc3, 0x5c, 0x40, 0xdb, 0xcc, 0x42, 0x2b, 0x68, 0xb5, 0xec, 0xc8, 0x6f, 0x6c, 0xc1,
|
||||
0x62, 0xc4, 0x1a, 0x11, 0x8b, 0xf7, 0xac, 0x19, 0x39, 0xac, 0x45, 0x6c, 0x43, 0x49, 0x18, 0x67,
|
||||
0x6e, 0x12, 0x5b, 0xb3, 0x2b, 0xb3, 0xab, 0x65, 0x27, 0x93, 0xf1, 0x2a, 0x1c, 0x8f, 0x58, 0xcc,
|
||||
0x3b, 0x91, 0xcb, 0xbe, 0xc2, 0xa2, 0xd8, 0xe7, 0x81, 0x35, 0x27, 0x57, 0x0f, 0x0e, 0x0b, 0x2d,
|
||||
0x31, 0x6b, 0x31, 0x37, 0xe1, 0x91, 0x35, 0x2f, 0xa7, 0x64, 0xb2, 0xc0, 0x23, 0x80, 0x5b, 0x0b,
|
||||
0x29, 0x1e, 0xf1, 0x4d, 0x5e, 0x86, 0xf2, 0x0e, 0xf7, 0xd8, 0x48, 0xc0, 0x64, 0x13, 0x4e, 0x39,
|
||||
0xac, 0xeb, 0x0b, 0xe5, 0xdb, 0x2c, 0xa1, 0x1e, 0x4d, 0xe8, 0xe0, 0xe4, 0x99, 0x6c, 0x77, 0x36,
|
||||
0x94, 0x22, 0x35, 0xd9, 0x9a, 0x91, 0xe3, 0x99, 0x4c, 0x7e, 0x81, 0x60, 0xd9, 0x70, 0x91, 0xa3,
|
||||
0x80, 0xdf, 0xee, 0xb2, 0x20, 0x89, 0x47, 0xab, 0xbc, 0x0a, 0x27, 0xf5, 0x1e, 0x77, 0x68, 0x9b,
|
||||
0xc5, 0x21, 0x75, 0x99, 0x72, 0xdd, 0xc1, 0x1f, 0x30, 0x81, 0x23, 0xe6, 0xa0, 0x35, 0x2b, 0x27,
|
||||
0xe6, 0xc6, 0xf0, 0x0a, 0x54, 0xb4, 0x7c, 0xff, 0xce, 0x1b, 0xca, 0x91, 0xe6, 0x10, 0x79, 0x0b,
|
||||
0x2c, 0x03, 0xe9, 0x36, 0x0d, 0xfc, 0x06, 0x8b, 0x93, 0x49, 0xb7, 0x8d, 0x72, 0xdb, 0x3e, 0x05,
|
||||
0x2f, 0xe4, 0x77, 0x1d, 0xf2, 0x20, 0x66, 0xe4, 0xb7, 0x28, 0x67, 0xe3, 0xf5, 0x88, 0xd1, 0x84,
|
||||
0x39, 0xec, 0x61, 0x87, 0xc5, 0x09, 0xde, 0x07, 0xf3, 0xa6, 0x49, 0x53, 0x95, 0xfa, 0x9d, 0x6a,
|
||||
0x9f, 0xaa, 0x55, 0x4d, 0x55, 0xf9, 0xf1, 0x75, 0xd7, 0xab, 0x76, 0xeb, 0xd5, 0x70, 0xbf, 0x59,
|
||||
0x15, 0xc4, 0xaf, 0x9a, 0x17, 0x57, 0x13, 0xbf, 0x6a, 0x82, 0x30, 0xb5, 0xe3, 0xd3, 0xb0, 0xd0,
|
||||
0x09, 0x63, 0x16, 0x25, 0x12, 0x7a, 0xc9, 0x51, 0x92, 0xd8, 0x54, 0x97, 0xb6, 0x7c, 0x8f, 0x26,
|
||||
0xa9, 0x1b, 0x4b, 0x4e, 0x26, 0x93, 0x0f, 0xf2, 0xe8, 0xef, 0x87, 0xde, 0x27, 0x85, 0xde, 0x44,
|
||||
0x39, 0x33, 0x80, 0xb2, 0x9b, 0x03, 0xf9, 0x06, 0x6b, 0xb1, 0x3e, 0xc8, 0x61, 0xc7, 0x68, 0xc1,
|
||||
0xa2, 0x4b, 0x63, 0x97, 0x7a, 0x5a, 0x95, 0x16, 0x05, 0x09, 0xc3, 0x88, 0x87, 0xb4, 0x29, 0x35,
|
||||
0xdd, 0xe5, 0x2d, 0xdf, 0xed, 0x29, 0x6e, 0x1d, 0xfc, 0x81, 0x9c, 0x83, 0xca, 0x6e, 0x2f, 0x70,
|
||||
0xdf, 0x0e, 0x65, 0x40, 0xc4, 0x2f, 0xc2, 0xbc, 0x9f, 0xb0, 0x76, 0x6c, 0x21, 0x79, 0xab, 0x53,
|
||||
0x81, 0xfc, 0x77, 0x0e, 0x4e, 0x1b, 0xe8, 0xc4, 0x82, 0x22, 0x6c, 0x05, 0x14, 0x13, 0x27, 0xe8,
|
||||
0x45, 0x3d, 0xa7, 0x13, 0xa8, 0x73, 0x52, 0x92, 0x30, 0x1c, 0x46, 0x9d, 0x80, 0x49, 0x8a, 0x97,
|
||||
0x9c, 0x54, 0xc0, 0x0d, 0x28, 0xc5, 0x89, 0x08, 0x81, 0xcd, 0x9e, 0x8c, 0x10, 0x95, 0xfa, 0x5b,
|
||||
0x87, 0x3b, 0x1b, 0x01, 0x7d, 0x57, 0x69, 0x74, 0x32, 0xdd, 0xf8, 0x21, 0x94, 0xf5, 0x9d, 0x8a,
|
||||
0xad, 0xc5, 0x95, 0xd9, 0xd5, 0x4a, 0x7d, 0xf7, 0xf0, 0x86, 0xde, 0x0e, 0x45, 0xf8, 0x36, 0xe2,
|
||||
0x87, 0xd3, 0xb7, 0x82, 0x97, 0xa0, 0xdc, 0x56, 0x97, 0x35, 0xb6, 0x4a, 0xd2, 0xdb, 0xfd, 0x01,
|
||||
0xfc, 0x55, 0x98, 0xf7, 0x83, 0x06, 0x8f, 0xad, 0xb2, 0x04, 0xf3, 0xda, 0xe1, 0xc0, 0xdc, 0x09,
|
||||
0x1a, 0xdc, 0x49, 0x15, 0xe2, 0x87, 0x70, 0x34, 0x62, 0x49, 0xd4, 0xd3, 0x5e, 0xb0, 0x40, 0xfa,
|
||||
0xf5, 0x4b, 0x87, 0xb3, 0xe0, 0x98, 0x2a, 0x9d, 0xbc, 0x05, 0xbc, 0x01, 0x95, 0xb8, 0xcf, 0x31,
|
||||
0xab, 0x22, 0x0d, 0x5a, 0x39, 0x45, 0x06, 0x07, 0x1d, 0x73, 0x32, 0xf9, 0x35, 0x82, 0xa5, 0x03,
|
||||
0xb7, 0x77, 0x37, 0x64, 0x85, 0x04, 0xa4, 0x30, 0x17, 0x87, 0xcc, 0x95, 0x61, 0xbd, 0x52, 0xdf,
|
||||
0x9e, 0xda, 0x75, 0x96, 0x76, 0xa5, 0xea, 0xc2, 0x88, 0x43, 0xe1, 0x53, 0xc6, 0xa2, 0xbb, 0x34,
|
||||
0x71, 0xf7, 0x8a, 0xd0, 0x0a, 0xea, 0x8b, 0x39, 0x2a, 0x0b, 0xa5, 0x82, 0xe0, 0x87, 0xfc, 0xb8,
|
||||
0xd7, 0x0b, 0x85, 0x05, 0xf1, 0x4b, 0x7f, 0x80, 0x04, 0x60, 0x9b, 0x61, 0x86, 0xb7, 0x5a, 0xef,
|
||||
0x52, 0x77, 0xbf, 0xc8, 0xca, 0x31, 0x98, 0xf1, 0x3d, 0x69, 0x62, 0xd6, 0x99, 0xf1, 0xbd, 0x67,
|
||||
0xbb, 0x88, 0xe2, 0xcd, 0x60, 0x0f, 0x49, 0x88, 0x45, 0x06, 0x97, 0xa0, 0x1c, 0x0c, 0x24, 0xc1,
|
||||
0xfe, 0xc0, 0x90, 0xe4, 0x37, 0x73, 0x20, 0xf9, 0x59, 0xb0, 0xd8, 0xcd, 0x5e, 0x10, 0xe2, 0x67,
|
||||
0x2d, 0x0a, 0x90, 0xcd, 0x88, 0x77, 0x42, 0xf5, 0x6c, 0x48, 0x05, 0x81, 0x62, 0xdf, 0x0f, 0x3c,
|
||||
0x6b, 0x21, 0x45, 0x21, 0xbe, 0xc9, 0x7f, 0x10, 0xbc, 0x3c, 0x04, 0xf8, 0xd8, 0x43, 0x79, 0x2e,
|
||||
0xd0, 0xf7, 0xa9, 0xb1, 0x38, 0x92, 0x1a, 0xa5, 0x41, 0x6a, 0xfc, 0x1b, 0xc1, 0xca, 0x90, 0x1d,
|
||||
0x8f, 0x4f, 0x29, 0xcf, 0xcd, 0x96, 0x1b, 0x3c, 0x72, 0x99, 0xb5, 0x98, 0xf2, 0x4f, 0x0a, 0x82,
|
||||
0xad, 0x3c, 0x0a, 0xf7, 0x68, 0x60, 0x95, 0x52, 0xb6, 0xa6, 0x12, 0xf9, 0x0b, 0x02, 0x4b, 0xef,
|
||||
0xf0, 0x96, 0x2b, 0xf7, 0xdb, 0x09, 0x9e, 0xff, 0x4d, 0x9e, 0x86, 0x05, 0x2a, 0xd1, 0xaa, 0x83,
|
||||
0x55, 0x12, 0xf9, 0x0e, 0x82, 0x33, 0xf9, 0xed, 0xc4, 0x5b, 0x7e, 0x9c, 0xe8, 0x97, 0x18, 0x6e,
|
||||
0xc0, 0x62, 0x3a, 0x33, 0x4d, 0xd0, 0x95, 0xfa, 0xd6, 0x61, 0xc3, 0x76, 0xce, 0x75, 0x5a, 0x39,
|
||||
0x79, 0x05, 0xce, 0x0c, 0xbd, 0xed, 0x0a, 0x86, 0x0d, 0x25, 0x9d, 0xaa, 0x94, 0x73, 0x33, 0x99,
|
||||
0xfc, 0x71, 0x36, 0x1f, 0xfd, 0xb8, 0xb7, 0xc5, 0x9b, 0x05, 0x6f, 0xe6, 0xe2, 0x03, 0xb1, 0x60,
|
||||
0x31, 0xe4, 0x9e, 0xf1, 0x3c, 0xd6, 0xa2, 0x58, 0xe7, 0xf2, 0x20, 0xa1, 0xa2, 0x12, 0x53, 0xef,
|
||||
0xe2, 0xfe, 0x80, 0x38, 0xc8, 0xd8, 0x0f, 0x5c, 0xb6, 0xcb, 0x5c, 0x1e, 0x78, 0xb1, 0x3c, 0x91,
|
||||
0x59, 0x27, 0x37, 0x86, 0xdf, 0x84, 0xb2, 0x94, 0xef, 0xf9, 0x6d, 0x26, 0xeb, 0x8c, 0x4a, 0x7d,
|
||||
0xad, 0x9a, 0x96, 0x79, 0x55, 0xb3, 0xcc, 0xeb, 0xfb, 0x50, 0x94, 0x79, 0xd5, 0xee, 0xf5, 0xaa,
|
||||
0x58, 0xe1, 0xf4, 0x17, 0x0b, 0x2c, 0x09, 0xf5, 0x5b, 0x5b, 0x7e, 0x20, 0x9f, 0x0f, 0xc2, 0x54,
|
||||
0x7f, 0x40, 0x1c, 0x76, 0x83, 0xb7, 0x5a, 0xfc, 0x91, 0xe6, 0x6e, 0x2a, 0x89, 0x55, 0x9d, 0x20,
|
||||
0xf1, 0x5b, 0xd2, 0x7e, 0x39, 0xdd, 0x41, 0x36, 0x20, 0x57, 0xf9, 0xad, 0x84, 0x45, 0x32, 0x41,
|
||||
0x97, 0x1d, 0x25, 0x65, 0x74, 0xaa, 0xa4, 0x75, 0x8f, 0xbe, 0x33, 0x29, 0xf1, 0x8e, 0x98, 0xc4,
|
||||
0x1b, 0x24, 0xf3, 0xd1, 0x21, 0xf5, 0x85, 0x2c, 0xe4, 0x58, 0xd7, 0xe7, 0x9d, 0xd8, 0x3a, 0x96,
|
||||
0xa6, 0x31, 0x2d, 0x93, 0xdf, 0x21, 0x28, 0x6d, 0xf1, 0xe6, 0xed, 0x20, 0x89, 0x7a, 0xf2, 0xbd,
|
||||
0xc9, 0x83, 0x84, 0x05, 0xfa, 0xc4, 0xb5, 0x28, 0xdc, 0x98, 0xf8, 0x6d, 0xb6, 0x9b, 0xd0, 0x76,
|
||||
0xa8, 0x32, 0xee, 0x33, 0xb9, 0x31, 0x5b, 0x2c, 0xb6, 0xd6, 0xa2, 0x71, 0x22, 0x6f, 0x5d, 0xc9,
|
||||
0x91, 0xdf, 0x62, 0x13, 0xd9, 0x84, 0xdd, 0x24, 0x52, 0x57, 0x2e, 0x37, 0x66, 0x92, 0x64, 0x3e,
|
||||
0xc5, 0xa6, 0x44, 0x52, 0x83, 0x97, 0xb2, 0x47, 0xd8, 0x3d, 0x16, 0xb5, 0xfd, 0x80, 0x16, 0xc6,
|
||||
0x40, 0x72, 0x3d, 0x47, 0x7c, 0xf1, 0x2a, 0x79, 0xe0, 0x07, 0x1e, 0x7f, 0x34, 0x9a, 0xc0, 0xe4,
|
||||
0x6f, 0xf9, 0x5a, 0xd1, 0x58, 0x93, 0xdd, 0x97, 0x37, 0xe1, 0xa8, 0xb8, 0x59, 0x5d, 0xa6, 0x7e,
|
||||
0x50, 0x97, 0x97, 0xe4, 0x2e, 0xe5, 0x50, 0x1d, 0x4e, 0x7e, 0x21, 0xde, 0x82, 0xe3, 0x34, 0x8e,
|
||||
0xfd, 0x66, 0xc0, 0x3c, 0xad, 0x6b, 0x66, 0x62, 0x5d, 0x83, 0x4b, 0xd3, 0x22, 0x42, 0xce, 0x50,
|
||||
0x3e, 0xd7, 0x22, 0xf9, 0x36, 0x82, 0x53, 0x43, 0x95, 0x64, 0xfc, 0x43, 0x46, 0x38, 0x13, 0x85,
|
||||
0xbc, 0xbb, 0xc7, 0xbc, 0x4e, 0x8b, 0xe9, 0x52, 0x5a, 0xcb, 0xe2, 0x37, 0xaf, 0x93, 0x9e, 0x80,
|
||||
0x0a, 0xa7, 0x99, 0x8c, 0x97, 0x01, 0xda, 0x34, 0xe8, 0xd0, 0x96, 0x84, 0x30, 0x27, 0x21, 0x18,
|
||||
0x23, 0x64, 0x09, 0xec, 0x61, 0xc7, 0xa7, 0xca, 0xd2, 0x5f, 0x21, 0x38, 0xa6, 0x43, 0x93, 0x3a,
|
||||
0x9f, 0x55, 0x38, 0x6e, 0xb8, 0x61, 0xa7, 0x7f, 0x54, 0x83, 0xc3, 0x63, 0xc2, 0x8e, 0x3e, 0xe7,
|
||||
0xd9, 0x7c, 0x37, 0xa4, 0x9b, 0xeb, 0x67, 0x4c, 0x1c, 0xf7, 0xb3, 0x8b, 0x4a, 0xbe, 0x05, 0xd6,
|
||||
0x36, 0x0d, 0x68, 0x93, 0x79, 0x19, 0xf0, 0x8c, 0x24, 0xdf, 0x30, 0x4b, 0xaf, 0x43, 0x17, 0x3a,
|
||||
0x59, 0xda, 0xf7, 0x1b, 0x0d, 0x55, 0xc6, 0xd5, 0xff, 0xb9, 0x0c, 0xd8, 0x3c, 0x54, 0x16, 0x75,
|
||||
0x7d, 0x97, 0xe1, 0x1f, 0x20, 0x98, 0x13, 0x59, 0x06, 0x9f, 0x1d, 0xc5, 0x21, 0xe9, 0x5c, 0x7b,
|
||||
0x7a, 0xef, 0x68, 0x61, 0x8d, 0x2c, 0xbd, 0xf7, 0xf7, 0x7f, 0xfd, 0x70, 0xe6, 0x34, 0x7e, 0x51,
|
||||
0xf6, 0xdd, 0xba, 0xd7, 0xcd, 0x1e, 0x58, 0x8c, 0xbf, 0x8b, 0x00, 0xab, 0xd4, 0x67, 0xb4, 0x5e,
|
||||
0xf0, 0x95, 0x51, 0x10, 0x87, 0xb4, 0x68, 0xec, 0xb3, 0x46, 0x18, 0xaa, 0xba, 0x3c, 0x62, 0x22,
|
||||
0xe8, 0xc8, 0x09, 0x12, 0xc0, 0x9a, 0x04, 0x70, 0x1e, 0x93, 0x61, 0x00, 0x6a, 0x8f, 0xc5, 0xa1,
|
||||
0x3f, 0xa9, 0xb1, 0xd4, 0xee, 0xcf, 0x11, 0xcc, 0x3f, 0x90, 0x8f, 0xaf, 0x31, 0x4e, 0xda, 0x9d,
|
||||
0x9a, 0x93, 0xa4, 0x39, 0x89, 0x96, 0x9c, 0x93, 0x48, 0xcf, 0xe2, 0x33, 0x1a, 0x69, 0x9c, 0x44,
|
||||
0x8c, 0xb6, 0x73, 0x80, 0xaf, 0x21, 0xfc, 0x21, 0x82, 0x85, 0xb4, 0x39, 0x83, 0x2f, 0x8c, 0x42,
|
||||
0x99, 0x6b, 0xde, 0xd8, 0xd3, 0xeb, 0x74, 0x90, 0xcb, 0x12, 0xe3, 0x39, 0x32, 0xf4, 0x38, 0x37,
|
||||
0x72, 0x7d, 0x90, 0xf7, 0x11, 0xcc, 0x6e, 0xb2, 0xb1, 0x7c, 0x9b, 0x22, 0xb8, 0x03, 0x0e, 0x1c,
|
||||
0x72, 0xd4, 0xf8, 0x03, 0x04, 0x2f, 0x6d, 0xb2, 0x64, 0x78, 0x2c, 0xc7, 0xab, 0xe3, 0x03, 0xac,
|
||||
0xa2, 0xdd, 0x95, 0x09, 0x66, 0x66, 0x41, 0xac, 0x26, 0x91, 0x5d, 0xc6, 0x97, 0x8a, 0x48, 0x28,
|
||||
0x0a, 0xe2, 0x47, 0x0a, 0xc7, 0x9f, 0x11, 0x9c, 0x18, 0x6c, 0x72, 0xe2, 0x7c, 0xf4, 0x1f, 0xda,
|
||||
0x03, 0xb5, 0x77, 0x0e, 0x1b, 0x50, 0xf2, 0x4a, 0xc9, 0x2d, 0x89, 0xfc, 0x55, 0xfc, 0x4a, 0x11,
|
||||
0x72, 0xdd, 0xf7, 0x89, 0x6b, 0x8f, 0xf5, 0xe7, 0x13, 0xd9, 0x2d, 0x97, 0xb0, 0xdf, 0x43, 0x70,
|
||||
0x64, 0x93, 0x25, 0xdb, 0x59, 0xdb, 0x63, 0x24, 0x6d, 0x73, 0x7d, 0x4d, 0x7b, 0xa9, 0x6a, 0x34,
|
||||
0xb5, 0xf5, 0x4f, 0x99, 0x4b, 0xd7, 0x25, 0xb0, 0x4b, 0xf8, 0x42, 0x11, 0xb0, 0x7e, 0xab, 0xe5,
|
||||
0xf7, 0x08, 0x16, 0xd2, 0xb6, 0xc2, 0x68, 0xf3, 0xb9, 0xa6, 0xe1, 0x34, 0x89, 0x79, 0x5b, 0x62,
|
||||
0xfd, 0xbc, 0x7d, 0x6d, 0x38, 0x56, 0x73, 0xbd, 0xf6, 0x5a, 0x55, 0x6e, 0x20, 0x7f, 0xa3, 0x3e,
|
||||
0x42, 0x00, 0xfd, 0xd6, 0x08, 0xbe, 0x5c, 0xbc, 0x0f, 0xa3, 0x7d, 0x62, 0x4f, 0xb7, 0x39, 0x42,
|
||||
0xaa, 0x72, 0x3f, 0xab, 0xf6, 0x4a, 0x21, 0x9d, 0x43, 0xe6, 0x6e, 0xa4, 0x6d, 0x94, 0x9f, 0x21,
|
||||
0x98, 0x97, 0xb5, 0x38, 0x3e, 0x3f, 0x0a, 0xb3, 0x59, 0xaa, 0x4f, 0xd3, 0xf5, 0x17, 0x25, 0xd4,
|
||||
0x95, 0x7a, 0x51, 0x4c, 0xd8, 0x40, 0x6b, 0xb8, 0x0b, 0x0b, 0x69, 0xed, 0x3c, 0x9a, 0x1e, 0xb9,
|
||||
0xda, 0xda, 0x5e, 0x29, 0xc8, 0x51, 0x29, 0x43, 0x55, 0x38, 0x5a, 0x1b, 0x17, 0x8e, 0xe6, 0x44,
|
||||
0xc4, 0xc0, 0xe7, 0x8a, 0xe2, 0xc9, 0xc7, 0xe0, 0x98, 0x2b, 0x12, 0xdd, 0x05, 0xb2, 0x32, 0x2e,
|
||||
0x24, 0x09, 0xef, 0xfc, 0x08, 0xc1, 0x89, 0xc1, 0x27, 0x0d, 0x3e, 0x33, 0x10, 0x8e, 0xcc, 0x37,
|
||||
0x9a, 0x9d, 0xf7, 0xe2, 0xa8, 0xe7, 0x10, 0xf9, 0x82, 0x44, 0xb1, 0x81, 0x6f, 0x8e, 0xbd, 0x19,
|
||||
0x3b, 0xfa, 0x42, 0x0b, 0x45, 0xeb, 0xfd, 0x0e, 0xeb, 0x6f, 0x10, 0x1c, 0xd1, 0x7a, 0xef, 0x45,
|
||||
0x8c, 0x15, 0xc3, 0x9a, 0xde, 0x45, 0x10, 0xb6, 0xc8, 0x67, 0x25, 0xfc, 0xcf, 0xe0, 0x1b, 0x13,
|
||||
0xc2, 0xd7, 0xb0, 0xd7, 0x13, 0x81, 0xf4, 0x0f, 0x08, 0x4e, 0x3e, 0x48, 0x79, 0xff, 0x09, 0xe1,
|
||||
0x7f, 0x5d, 0xe2, 0xff, 0x1c, 0x7e, 0xb5, 0xe0, 0xc9, 0x31, 0x6e, 0x1b, 0xd7, 0x10, 0xfe, 0x25,
|
||||
0x82, 0x92, 0xee, 0x4e, 0xe2, 0x4b, 0x23, 0x2f, 0x46, 0xbe, 0x7f, 0x39, 0x4d, 0x32, 0xab, 0xfc,
|
||||
0x4a, 0xce, 0x17, 0x66, 0x29, 0x65, 0x5f, 0x10, 0xfa, 0x7d, 0x04, 0x38, 0xab, 0x35, 0xb2, 0xea,
|
||||
0x03, 0x5f, 0xcc, 0x99, 0x1a, 0x59, 0x54, 0xda, 0x97, 0xc6, 0xce, 0xcb, 0x67, 0xa9, 0xb5, 0xc2,
|
||||
0x2c, 0xc5, 0x33, 0xfb, 0xdf, 0x43, 0x50, 0xd9, 0x64, 0xd9, 0x73, 0xb8, 0xc0, 0x97, 0xf9, 0xd6,
|
||||
0xac, 0xbd, 0x3a, 0x7e, 0xa2, 0x42, 0x74, 0x55, 0x22, 0xba, 0x88, 0x8b, 0x5d, 0xa5, 0x01, 0xfc,
|
||||
0x04, 0xc1, 0xd1, 0xbb, 0x26, 0x45, 0xf1, 0xd5, 0x71, 0x96, 0x72, 0x91, 0x7c, 0x72, 0x5c, 0x9f,
|
||||
0x96, 0xb8, 0xd6, 0xc9, 0x44, 0xb8, 0x36, 0x54, 0x8f, 0xf4, 0xa7, 0x08, 0x5e, 0x30, 0xeb, 0x07,
|
||||
0xd5, 0x4d, 0xfb, 0x7f, 0xfd, 0x56, 0xd0, 0x94, 0x23, 0x37, 0x24, 0xbe, 0x2a, 0xbe, 0x3a, 0x09,
|
||||
0xbe, 0x9a, 0x6a, 0xb1, 0xe1, 0x1f, 0x23, 0x38, 0x29, 0x7b, 0x95, 0xa6, 0xe2, 0x81, 0x14, 0x33,
|
||||
0xaa, 0xb3, 0x39, 0x41, 0x8a, 0x51, 0xf1, 0x87, 0x3c, 0x13, 0xa8, 0x0d, 0xd5, 0x87, 0xc4, 0xdf,
|
||||
0x47, 0x70, 0x4c, 0x27, 0x35, 0x75, 0xba, 0xeb, 0xe3, 0x1c, 0xf7, 0xac, 0x49, 0x50, 0xd1, 0x6d,
|
||||
0x6d, 0x32, 0xba, 0x7d, 0x88, 0x60, 0x51, 0xf5, 0x12, 0x0b, 0x9e, 0x0a, 0x46, 0xb3, 0xd1, 0x3e,
|
||||
0x95, 0x9b, 0xa5, 0x1b, 0x59, 0xe4, 0x6b, 0xd2, 0xec, 0x7d, 0x5c, 0x2b, 0x32, 0x1b, 0x72, 0x2f,
|
||||
0xae, 0x3d, 0x56, 0x5d, 0xa4, 0x27, 0xb5, 0x16, 0x6f, 0xc6, 0xef, 0x10, 0x5c, 0x98, 0x10, 0xc5,
|
||||
0x9c, 0x6b, 0xe8, 0xb5, 0x2f, 0xfe, 0xe9, 0xe9, 0x32, 0xfa, 0xeb, 0xd3, 0x65, 0xf4, 0x8f, 0xa7,
|
||||
0xcb, 0xe8, 0x9d, 0x9b, 0x93, 0xfd, 0xff, 0xc3, 0x6d, 0xf9, 0x2c, 0x48, 0x4c, 0xb5, 0xff, 0x0b,
|
||||
0x00, 0x00, 0xff, 0xff, 0xa3, 0x87, 0x84, 0x43, 0xe5, 0x22, 0x00, 0x00,
|
||||
0x5c, 0x56, 0x48, 0x5c, 0xe0, 0xc2, 0x87, 0xc4, 0x61, 0x05, 0xff, 0x00, 0x44, 0x9c, 0xb8, 0x70,
|
||||
0xe5, 0x88, 0xaa, 0xba, 0xaa, 0x5d, 0xed, 0xb1, 0xdb, 0x0e, 0x63, 0xb4, 0xb9, 0xf5, 0x2b, 0x57,
|
||||
0xbd, 0xf7, 0x7b, 0xaf, 0xde, 0x47, 0xbd, 0x27, 0xc3, 0xf9, 0x98, 0x45, 0x5d, 0x16, 0xd5, 0x68,
|
||||
0x18, 0xb6, 0x7c, 0x97, 0x26, 0x3e, 0x0f, 0xcc, 0xef, 0x6a, 0x18, 0xf1, 0x84, 0xe3, 0x8a, 0xb1,
|
||||
0x64, 0x2f, 0x35, 0x39, 0x6f, 0xb6, 0x58, 0x8d, 0x86, 0x7e, 0x8d, 0x06, 0x01, 0x4f, 0xe4, 0x72,
|
||||
0x9c, 0x6e, 0xb5, 0xc9, 0xfe, 0xcd, 0xb8, 0xea, 0x73, 0xf9, 0xab, 0xcb, 0x23, 0x56, 0xeb, 0x5e,
|
||||
0xaf, 0x35, 0x59, 0xc0, 0x22, 0x9a, 0x30, 0x4f, 0xed, 0xb9, 0xd1, 0xdf, 0xd3, 0xa6, 0xee, 0x9e,
|
||||
0x1f, 0xb0, 0xa8, 0x57, 0x0b, 0xf7, 0x9b, 0x62, 0x21, 0xae, 0xb5, 0x59, 0x42, 0x87, 0x9d, 0xda,
|
||||
0x6a, 0xfa, 0xc9, 0x5e, 0xe7, 0xdd, 0xaa, 0xcb, 0xdb, 0x35, 0x1a, 0x35, 0x79, 0x18, 0xf1, 0x6f,
|
||||
0xca, 0x8f, 0x75, 0xd7, 0xab, 0x75, 0xeb, 0x7d, 0x06, 0xa6, 0x2e, 0xdd, 0xeb, 0xb4, 0x15, 0xee,
|
||||
0xd1, 0x83, 0xdc, 0x6e, 0x8f, 0xe1, 0x16, 0xb1, 0x90, 0x2b, 0xdb, 0xc8, 0x4f, 0x3f, 0xe1, 0x51,
|
||||
0xcf, 0xf8, 0x4c, 0xd9, 0x90, 0x8f, 0x11, 0x9c, 0xb8, 0xd5, 0x97, 0xf7, 0xe5, 0x0e, 0x8b, 0x7a,
|
||||
0x18, 0xc3, 0x5c, 0x40, 0xdb, 0xcc, 0x42, 0x2b, 0x68, 0xb5, 0xec, 0xc8, 0x6f, 0x6c, 0xc1, 0x62,
|
||||
0xc4, 0x1a, 0x11, 0x8b, 0xf7, 0xac, 0x19, 0xb9, 0xac, 0x49, 0x6c, 0x43, 0x49, 0x08, 0x67, 0x6e,
|
||||
0x12, 0x5b, 0xb3, 0x2b, 0xb3, 0xab, 0x65, 0x27, 0xa3, 0xf1, 0x2a, 0x1c, 0x8f, 0x58, 0xcc, 0x3b,
|
||||
0x91, 0xcb, 0xbe, 0xc2, 0xa2, 0xd8, 0xe7, 0x81, 0x35, 0x27, 0x4f, 0x0f, 0x2e, 0x0b, 0x2e, 0x31,
|
||||
0x6b, 0x31, 0x37, 0xe1, 0x91, 0x35, 0x2f, 0xb7, 0x64, 0xb4, 0xc0, 0x23, 0x80, 0x5b, 0x0b, 0x29,
|
||||
0x1e, 0xf1, 0x2d, 0xf0, 0x28, 0x29, 0x56, 0x49, 0x0a, 0xd5, 0x24, 0x79, 0x19, 0xca, 0x3b, 0xdc,
|
||||
0x63, 0x23, 0x55, 0x21, 0x9b, 0x70, 0xca, 0x61, 0x5d, 0x5f, 0x88, 0xdd, 0x66, 0x09, 0xf5, 0x68,
|
||||
0x42, 0x07, 0x37, 0xcf, 0x64, 0x7a, 0xdb, 0x50, 0x8a, 0xd4, 0x66, 0x6b, 0x46, 0xae, 0x67, 0x34,
|
||||
0xf9, 0x05, 0x82, 0x65, 0xc3, 0x78, 0x8e, 0x52, 0xe9, 0x76, 0x97, 0x05, 0x49, 0x3c, 0x9a, 0xe5,
|
||||
0x55, 0x38, 0xa9, 0xb5, 0xdf, 0xa1, 0x6d, 0x16, 0x87, 0xd4, 0x65, 0xca, 0xa8, 0x07, 0x7f, 0xc0,
|
||||
0x04, 0x8e, 0x98, 0x8b, 0xd6, 0xac, 0xdc, 0x98, 0x5b, 0xc3, 0x2b, 0x50, 0xd1, 0xf4, 0xfd, 0x3b,
|
||||
0x6f, 0x28, 0x13, 0x9b, 0x4b, 0xe4, 0x2d, 0xb0, 0x0c, 0xa4, 0xdb, 0x34, 0xf0, 0x1b, 0x2c, 0x4e,
|
||||
0x26, 0x55, 0x1b, 0xe5, 0xd4, 0x3e, 0x05, 0x2f, 0xe4, 0xb5, 0x0e, 0x79, 0x10, 0x33, 0xf2, 0x5b,
|
||||
0x94, 0x93, 0xf1, 0x7a, 0xc4, 0x68, 0xc2, 0x1c, 0xf6, 0xb0, 0xc3, 0xe2, 0x04, 0xef, 0x83, 0x19,
|
||||
0x83, 0x52, 0x54, 0xa5, 0x7e, 0xa7, 0xda, 0x77, 0xe2, 0xaa, 0x76, 0x62, 0xf9, 0xf1, 0x75, 0xd7,
|
||||
0xab, 0x76, 0xeb, 0xd5, 0x70, 0xbf, 0x59, 0x15, 0x21, 0x51, 0x35, 0x43, 0x5a, 0x87, 0x44, 0xd5,
|
||||
0x04, 0x61, 0x72, 0xc7, 0xa7, 0x61, 0xa1, 0x13, 0xc6, 0x2c, 0x4a, 0x24, 0xf4, 0x92, 0xa3, 0x28,
|
||||
0xa1, 0x54, 0x97, 0xb6, 0x7c, 0x8f, 0x26, 0xa9, 0x19, 0x4b, 0x4e, 0x46, 0x93, 0x0f, 0xf2, 0xe8,
|
||||
0xef, 0x87, 0xde, 0x27, 0x85, 0xde, 0x44, 0x39, 0x33, 0x80, 0xb2, 0x9b, 0x03, 0xf9, 0x06, 0x6b,
|
||||
0xb1, 0x3e, 0xc8, 0x61, 0xd7, 0x68, 0xc1, 0xa2, 0x4b, 0x63, 0x97, 0x7a, 0x9a, 0x95, 0x26, 0x85,
|
||||
0x13, 0x86, 0x11, 0x0f, 0x69, 0x53, 0x72, 0xba, 0xcb, 0x5b, 0xbe, 0xdb, 0x53, 0xbe, 0x75, 0xf0,
|
||||
0x07, 0x72, 0x0e, 0x2a, 0xbb, 0xbd, 0xc0, 0x7d, 0x3b, 0x94, 0xa9, 0x12, 0xbf, 0x08, 0xf3, 0x7e,
|
||||
0xc2, 0xda, 0xb1, 0x85, 0x64, 0xe8, 0xa5, 0x04, 0xf9, 0xcf, 0x1c, 0x9c, 0x36, 0xd0, 0x89, 0x03,
|
||||
0x45, 0xd8, 0x0a, 0x5c, 0x4c, 0xdc, 0xa0, 0x17, 0xf5, 0x9c, 0x4e, 0xa0, 0xee, 0x49, 0x51, 0x42,
|
||||
0x70, 0x18, 0x75, 0x02, 0x26, 0x5d, 0xbc, 0xe4, 0xa4, 0x04, 0x6e, 0x40, 0x29, 0x4e, 0x44, 0x72,
|
||||
0x6c, 0xf6, 0x64, 0xee, 0xa8, 0xd4, 0xdf, 0x3a, 0xdc, 0xdd, 0x08, 0xe8, 0xbb, 0x8a, 0xa3, 0x93,
|
||||
0xf1, 0xc6, 0x0f, 0xa1, 0xac, 0x63, 0x2a, 0xb6, 0x16, 0x57, 0x66, 0x57, 0x2b, 0xf5, 0xdd, 0xc3,
|
||||
0x0b, 0x7a, 0x3b, 0x14, 0x89, 0xdd, 0xc8, 0x1f, 0x4e, 0x5f, 0x0a, 0x5e, 0x82, 0x72, 0x5b, 0x05,
|
||||
0x6b, 0xac, 0x12, 0x5d, 0x7f, 0x01, 0x7f, 0x15, 0xe6, 0xfd, 0xa0, 0xc1, 0x63, 0xab, 0x2c, 0xc1,
|
||||
0xbc, 0x76, 0x38, 0x30, 0x77, 0x82, 0x06, 0x77, 0x52, 0x86, 0xf8, 0x21, 0x1c, 0x8d, 0x58, 0x12,
|
||||
0xf5, 0xb4, 0x15, 0x2c, 0x90, 0x76, 0xfd, 0xd2, 0xe1, 0x24, 0x38, 0x26, 0x4b, 0x27, 0x2f, 0x01,
|
||||
0x6f, 0x40, 0x25, 0xee, 0xfb, 0x98, 0x55, 0x91, 0x02, 0xad, 0x1c, 0x23, 0xc3, 0x07, 0x1d, 0x73,
|
||||
0x33, 0xf9, 0x35, 0x82, 0xa5, 0x03, 0xd1, 0xbb, 0x1b, 0xb2, 0x42, 0x07, 0xa4, 0x30, 0x17, 0x87,
|
||||
0xcc, 0x95, 0x69, 0xbd, 0x52, 0xdf, 0x9e, 0x5a, 0x38, 0x4b, 0xb9, 0x92, 0x75, 0x61, 0xc6, 0xa1,
|
||||
0xf0, 0x29, 0xe3, 0xd0, 0x5d, 0x9a, 0xb8, 0x7b, 0x45, 0x68, 0x85, 0xeb, 0x8b, 0x3d, 0xaa, 0x0a,
|
||||
0xa5, 0x84, 0xf0, 0x0f, 0xf9, 0x71, 0xaf, 0x17, 0x0a, 0x09, 0xe2, 0x97, 0xfe, 0x02, 0x09, 0xc0,
|
||||
0x36, 0xd3, 0x0c, 0x6f, 0xb5, 0xde, 0xa5, 0xee, 0x7e, 0x91, 0x94, 0x63, 0x30, 0xe3, 0x7b, 0x52,
|
||||
0xc4, 0xac, 0x33, 0xe3, 0x7b, 0xcf, 0x16, 0x88, 0xe4, 0x23, 0x94, 0x17, 0xa8, 0x1d, 0xba, 0x40,
|
||||
0xe0, 0x12, 0x94, 0x83, 0x81, 0x22, 0xd8, 0x5f, 0x18, 0x52, 0xfc, 0x66, 0x0e, 0x14, 0x3f, 0x0b,
|
||||
0x16, 0xbb, 0xd9, 0xdb, 0x42, 0xfc, 0xac, 0x49, 0x01, 0xb2, 0x19, 0xf1, 0x4e, 0xa8, 0x1e, 0x14,
|
||||
0x29, 0x21, 0x50, 0xec, 0xfb, 0x81, 0x67, 0x2d, 0xa4, 0x28, 0xc4, 0x37, 0xf9, 0x37, 0x82, 0x97,
|
||||
0x87, 0x00, 0x1f, 0x7b, 0x29, 0xcf, 0x05, 0xfa, 0xbe, 0x6b, 0x2c, 0x8e, 0x74, 0x8d, 0xd2, 0xa0,
|
||||
0x6b, 0xfc, 0x0b, 0xc1, 0xca, 0x10, 0x8d, 0xc7, 0x97, 0x94, 0xe7, 0x46, 0xe5, 0x06, 0x8f, 0x5c,
|
||||
0x66, 0x2d, 0xa6, 0xfe, 0x27, 0x09, 0xe1, 0xad, 0x3c, 0x0a, 0xf7, 0x68, 0x60, 0x95, 0x52, 0x6f,
|
||||
0x4d, 0x29, 0xf2, 0x17, 0x04, 0x96, 0xd6, 0xf0, 0x96, 0x2b, 0xf5, 0xed, 0x04, 0xcf, 0xbf, 0x92,
|
||||
0xa7, 0x61, 0x81, 0x4a, 0xb4, 0xea, 0x62, 0x15, 0x45, 0xbe, 0x83, 0xe0, 0x4c, 0x5e, 0x9d, 0x78,
|
||||
0xcb, 0x8f, 0x13, 0xfd, 0x12, 0xc3, 0x0d, 0x58, 0x4c, 0x77, 0xa6, 0x05, 0xba, 0x52, 0xdf, 0x3a,
|
||||
0x6c, 0xda, 0xce, 0x99, 0x4e, 0x33, 0x27, 0xaf, 0xc0, 0x99, 0xa1, 0xd1, 0xae, 0x60, 0xd8, 0x50,
|
||||
0xd2, 0xa5, 0x4a, 0x19, 0x37, 0xa3, 0xc9, 0x1f, 0x67, 0xf3, 0xd9, 0x8f, 0x7b, 0x5b, 0xbc, 0x59,
|
||||
0xf0, 0x66, 0x2e, 0xbe, 0x10, 0xd1, 0x0c, 0x70, 0xcf, 0x78, 0x1e, 0x6b, 0x52, 0x9c, 0x73, 0x79,
|
||||
0x90, 0x50, 0xd1, 0xa3, 0xa9, 0x77, 0x71, 0x7f, 0x41, 0x5c, 0x64, 0xec, 0x07, 0x2e, 0xdb, 0x65,
|
||||
0x2e, 0x0f, 0xbc, 0x58, 0xde, 0xc8, 0xac, 0x93, 0x5b, 0xc3, 0x6f, 0x42, 0x59, 0xd2, 0xf7, 0xfc,
|
||||
0x36, 0x93, 0x1d, 0x48, 0xa5, 0xbe, 0x56, 0x4d, 0x1b, 0xc0, 0xaa, 0xd9, 0x00, 0xf6, 0x6d, 0x28,
|
||||
0x1a, 0xc0, 0x6a, 0xf7, 0x7a, 0x55, 0x9c, 0x70, 0xfa, 0x87, 0x05, 0x96, 0x84, 0xfa, 0xad, 0x2d,
|
||||
0x3f, 0x90, 0xcf, 0x07, 0x21, 0xaa, 0xbf, 0x20, 0x2e, 0xbb, 0xc1, 0x5b, 0x2d, 0xfe, 0x48, 0xfb,
|
||||
0x6e, 0x4a, 0x89, 0x53, 0x9d, 0x20, 0xf1, 0x5b, 0x52, 0x7e, 0x39, 0xd5, 0x20, 0x5b, 0x90, 0xa7,
|
||||
0xfc, 0x56, 0xc2, 0x22, 0x59, 0xa0, 0xcb, 0x8e, 0xa2, 0x32, 0x77, 0xaa, 0xa4, 0x7d, 0x8f, 0x8e,
|
||||
0x99, 0xd4, 0xf1, 0x8e, 0x98, 0x8e, 0x37, 0xe8, 0xcc, 0x47, 0x87, 0xf4, 0x17, 0xb2, 0xc5, 0x63,
|
||||
0x5d, 0x9f, 0x77, 0x62, 0xeb, 0x58, 0x5a, 0xc6, 0x34, 0x4d, 0x7e, 0x87, 0xa0, 0xb4, 0xc5, 0x9b,
|
||||
0xb7, 0x83, 0x24, 0xea, 0xc9, 0xf7, 0x26, 0x0f, 0x12, 0x16, 0xe8, 0x1b, 0xd7, 0xa4, 0x30, 0x63,
|
||||
0xe2, 0xb7, 0xd9, 0x6e, 0x42, 0xdb, 0xa1, 0xaa, 0xb8, 0xcf, 0x64, 0xc6, 0xec, 0xb0, 0x50, 0xad,
|
||||
0x45, 0xe3, 0x44, 0x46, 0x5d, 0xc9, 0x91, 0xdf, 0x42, 0x89, 0x6c, 0xc3, 0x6e, 0x12, 0xa9, 0x90,
|
||||
0xcb, 0xad, 0x99, 0x4e, 0x32, 0x9f, 0x62, 0x53, 0x24, 0xa9, 0xc1, 0x4b, 0xd9, 0x23, 0xec, 0x1e,
|
||||
0x8b, 0xda, 0x7e, 0x40, 0x0b, 0x73, 0x20, 0xb9, 0x9e, 0x73, 0x7c, 0xf1, 0x2a, 0x79, 0xe0, 0x07,
|
||||
0x1e, 0x7f, 0x34, 0xda, 0x81, 0xc9, 0x5f, 0xf3, 0xbd, 0xa2, 0x71, 0x26, 0x8b, 0x97, 0x37, 0xe1,
|
||||
0xa8, 0x88, 0xac, 0x2e, 0x53, 0x3f, 0xa8, 0xe0, 0x25, 0xb9, 0xa0, 0x1c, 0xca, 0xc3, 0xc9, 0x1f,
|
||||
0xc4, 0x5b, 0x70, 0x9c, 0xc6, 0xb1, 0xdf, 0x0c, 0x98, 0xa7, 0x79, 0xcd, 0x4c, 0xcc, 0x6b, 0xf0,
|
||||
0x68, 0xda, 0x44, 0xc8, 0x1d, 0xca, 0xe6, 0x9a, 0x24, 0xdf, 0x46, 0x70, 0x6a, 0x28, 0x93, 0xcc,
|
||||
0xff, 0x90, 0x91, 0xce, 0x44, 0x8b, 0xef, 0xee, 0x31, 0xaf, 0xd3, 0x62, 0xba, 0x95, 0xd6, 0xb4,
|
||||
0xf8, 0xcd, 0xeb, 0xa4, 0x37, 0xa0, 0xd2, 0x69, 0x46, 0xe3, 0x65, 0x80, 0x36, 0x0d, 0x3a, 0xb4,
|
||||
0x25, 0x21, 0xcc, 0x49, 0x08, 0xc6, 0x0a, 0x59, 0x02, 0x7b, 0xd8, 0xf5, 0xa9, 0xb6, 0xf4, 0x57,
|
||||
0x08, 0x8e, 0xe9, 0xd4, 0xa4, 0xee, 0x67, 0x15, 0x8e, 0x1b, 0x66, 0xd8, 0xe9, 0x5f, 0xd5, 0xe0,
|
||||
0xf2, 0x98, 0xb4, 0xa3, 0xef, 0x79, 0x36, 0x3f, 0x27, 0xe9, 0xe6, 0x26, 0x1d, 0x13, 0xe7, 0xfd,
|
||||
0x2c, 0x50, 0xc9, 0xb7, 0xc0, 0xda, 0xa6, 0x01, 0x6d, 0x32, 0x2f, 0x03, 0x9e, 0x39, 0xc9, 0x37,
|
||||
0xcc, 0xd6, 0xeb, 0xd0, 0x8d, 0x4e, 0x56, 0xf6, 0xfd, 0x46, 0x43, 0xb5, 0x71, 0xf5, 0x7f, 0x2c,
|
||||
0x03, 0x36, 0x2f, 0x95, 0x45, 0x5d, 0xdf, 0x65, 0xf8, 0x07, 0x08, 0xe6, 0x44, 0x95, 0xc1, 0x67,
|
||||
0x47, 0xf9, 0x90, 0x34, 0xae, 0x3d, 0xbd, 0x77, 0xb4, 0x90, 0x46, 0x96, 0xde, 0xfb, 0xdb, 0x3f,
|
||||
0x7f, 0x38, 0x73, 0x1a, 0xbf, 0x28, 0x27, 0x72, 0xdd, 0xeb, 0xe6, 0x74, 0x2c, 0xc6, 0xdf, 0x45,
|
||||
0x80, 0x55, 0xe9, 0x33, 0x46, 0x2f, 0xf8, 0xca, 0x28, 0x88, 0x43, 0x46, 0x34, 0xf6, 0x59, 0x23,
|
||||
0x0d, 0x55, 0x5d, 0x1e, 0x31, 0x91, 0x74, 0xe4, 0x06, 0x09, 0x60, 0x4d, 0x02, 0x38, 0x8f, 0xc9,
|
||||
0x30, 0x00, 0xb5, 0xc7, 0xe2, 0xd2, 0x9f, 0xd4, 0x58, 0x2a, 0xf7, 0xe7, 0x08, 0xe6, 0x1f, 0xc8,
|
||||
0xc7, 0xd7, 0x18, 0x23, 0xed, 0x4e, 0xcd, 0x48, 0x52, 0x9c, 0x44, 0x4b, 0xce, 0x49, 0xa4, 0x67,
|
||||
0xf1, 0x19, 0x8d, 0x34, 0x4e, 0x22, 0x46, 0xdb, 0x39, 0xc0, 0xd7, 0x10, 0xfe, 0x10, 0xc1, 0x42,
|
||||
0x3a, 0x9c, 0xc1, 0x17, 0x46, 0xa1, 0xcc, 0x0d, 0x6f, 0xec, 0xe9, 0x4d, 0x3a, 0xc8, 0x65, 0x89,
|
||||
0xf1, 0x1c, 0x19, 0x7a, 0x9d, 0x1b, 0xb9, 0x39, 0xc8, 0xfb, 0x08, 0x66, 0x37, 0xd9, 0x58, 0x7f,
|
||||
0x9b, 0x22, 0xb8, 0x03, 0x06, 0x1c, 0x72, 0xd5, 0xf8, 0x03, 0x04, 0x2f, 0x6d, 0xb2, 0x64, 0x78,
|
||||
0x2e, 0xc7, 0xab, 0xe3, 0x13, 0xac, 0x72, 0xbb, 0x2b, 0x13, 0xec, 0xcc, 0x92, 0x58, 0x4d, 0x22,
|
||||
0xbb, 0x8c, 0x2f, 0x15, 0x39, 0xa1, 0x68, 0x88, 0x1f, 0x29, 0x1c, 0x7f, 0x46, 0x70, 0x62, 0x70,
|
||||
0xc8, 0x89, 0xf3, 0xd9, 0x7f, 0xe8, 0x0c, 0xd4, 0xde, 0x39, 0x6c, 0x42, 0xc9, 0x33, 0x25, 0xb7,
|
||||
0x24, 0xf2, 0x57, 0xf1, 0x2b, 0x45, 0xc8, 0xf5, 0xdc, 0x27, 0xae, 0x3d, 0xd6, 0x9f, 0x4f, 0xe4,
|
||||
0x1c, 0x5d, 0xc2, 0x7e, 0x0f, 0xc1, 0x91, 0x4d, 0x96, 0x6c, 0x67, 0x63, 0x8f, 0x91, 0x6e, 0x9b,
|
||||
0x9b, 0x6b, 0xda, 0x4b, 0x55, 0x63, 0xdc, 0xad, 0x7f, 0xca, 0x4c, 0xba, 0x2e, 0x81, 0x5d, 0xc2,
|
||||
0x17, 0x8a, 0x80, 0xf5, 0x47, 0x2d, 0xbf, 0x47, 0xb0, 0x90, 0x8e, 0x15, 0x46, 0x8b, 0xcf, 0x0d,
|
||||
0x0d, 0xa7, 0xe9, 0x98, 0xb7, 0x25, 0xd6, 0xcf, 0xdb, 0xd7, 0x86, 0x63, 0x35, 0xcf, 0x6b, 0xab,
|
||||
0x55, 0xa5, 0x02, 0xf9, 0x88, 0xfa, 0x08, 0x01, 0xf4, 0x47, 0x23, 0xf8, 0x72, 0xb1, 0x1e, 0xc6,
|
||||
0xf8, 0xc4, 0x9e, 0xee, 0x70, 0x84, 0x54, 0xa5, 0x3e, 0xab, 0xf6, 0x4a, 0xa1, 0x3b, 0x87, 0xcc,
|
||||
0xdd, 0x48, 0xc7, 0x28, 0x3f, 0x43, 0x30, 0x2f, 0x7b, 0x71, 0x7c, 0x7e, 0x14, 0x66, 0xb3, 0x55,
|
||||
0x9f, 0xa6, 0xe9, 0x2f, 0x4a, 0xa8, 0x2b, 0xf5, 0xa2, 0x9c, 0xb0, 0x81, 0xd6, 0x70, 0x17, 0x16,
|
||||
0xd2, 0xde, 0x79, 0xb4, 0x7b, 0xe4, 0x7a, 0x6b, 0x7b, 0xa5, 0xa0, 0x46, 0xa5, 0x1e, 0xaa, 0xd2,
|
||||
0xd1, 0xda, 0xb8, 0x74, 0x34, 0x27, 0x32, 0x06, 0x3e, 0x57, 0x94, 0x4f, 0xfe, 0x0f, 0x86, 0xb9,
|
||||
0x22, 0xd1, 0x5d, 0x20, 0x2b, 0xe3, 0x52, 0x92, 0xb0, 0xce, 0x8f, 0x10, 0x9c, 0x18, 0x7c, 0xd2,
|
||||
0xe0, 0x33, 0x03, 0xe9, 0xc8, 0x7c, 0xa3, 0xd9, 0x79, 0x2b, 0x8e, 0x7a, 0x0e, 0x91, 0x2f, 0x48,
|
||||
0x14, 0x1b, 0xf8, 0xe6, 0xd8, 0xc8, 0xd8, 0xd1, 0x01, 0x2d, 0x18, 0xad, 0xf7, 0x27, 0xac, 0xbf,
|
||||
0x41, 0x70, 0x44, 0xf3, 0xbd, 0x17, 0x31, 0x56, 0x0c, 0x6b, 0x7a, 0x81, 0x20, 0x64, 0x91, 0xcf,
|
||||
0x4a, 0xf8, 0x9f, 0xc1, 0x37, 0x26, 0x84, 0xaf, 0x61, 0xaf, 0x27, 0x02, 0xe9, 0x1f, 0x10, 0x9c,
|
||||
0x7c, 0x90, 0xfa, 0xfd, 0x27, 0x84, 0xff, 0x75, 0x89, 0xff, 0x73, 0xf8, 0xd5, 0x82, 0x27, 0xc7,
|
||||
0x38, 0x35, 0xae, 0x21, 0xfc, 0x4b, 0x04, 0x25, 0x3d, 0x9d, 0xc4, 0x97, 0x46, 0x06, 0x46, 0x7e,
|
||||
0x7e, 0x39, 0x4d, 0x67, 0x56, 0xf5, 0x95, 0x9c, 0x2f, 0xac, 0x52, 0x4a, 0xbe, 0x70, 0xe8, 0xf7,
|
||||
0x11, 0xe0, 0xac, 0xd7, 0xc8, 0xba, 0x0f, 0x7c, 0x31, 0x27, 0x6a, 0x64, 0x53, 0x69, 0x5f, 0x1a,
|
||||
0xbb, 0x2f, 0x5f, 0xa5, 0xd6, 0x0a, 0xab, 0x14, 0xcf, 0xe4, 0x7f, 0x0f, 0x41, 0x65, 0x93, 0x65,
|
||||
0xcf, 0xe1, 0x02, 0x5b, 0xe6, 0x47, 0xb3, 0xf6, 0xea, 0xf8, 0x8d, 0x0a, 0xd1, 0x55, 0x89, 0xe8,
|
||||
0x22, 0x2e, 0x36, 0x95, 0x06, 0xf0, 0x13, 0x04, 0x47, 0xef, 0x9a, 0x2e, 0x8a, 0xaf, 0x8e, 0x93,
|
||||
0x94, 0xcb, 0xe4, 0x93, 0xe3, 0xfa, 0xb4, 0xc4, 0xb5, 0x4e, 0x26, 0xc2, 0xb5, 0xa1, 0x66, 0xa4,
|
||||
0x3f, 0x45, 0xf0, 0x82, 0xd9, 0x3f, 0xa8, 0x69, 0xda, 0xff, 0x6a, 0xb7, 0x82, 0xa1, 0x1c, 0xb9,
|
||||
0x21, 0xf1, 0x55, 0xf1, 0xd5, 0x49, 0xf0, 0xd5, 0xd4, 0x88, 0x0d, 0xff, 0x18, 0xc1, 0x49, 0x39,
|
||||
0xab, 0x34, 0x19, 0x0f, 0x94, 0x98, 0x51, 0x93, 0xcd, 0x09, 0x4a, 0x8c, 0xca, 0x3f, 0xe4, 0x99,
|
||||
0x40, 0x6d, 0xa8, 0x39, 0x24, 0xfe, 0x3e, 0x82, 0x63, 0xba, 0xa8, 0xa9, 0xdb, 0x5d, 0x1f, 0x67,
|
||||
0xb8, 0x67, 0x2d, 0x82, 0xca, 0xdd, 0xd6, 0x26, 0x73, 0xb7, 0x0f, 0x11, 0x2c, 0xaa, 0x59, 0x62,
|
||||
0xc1, 0x53, 0xc1, 0x18, 0x36, 0xda, 0xa7, 0x72, 0xbb, 0xf4, 0x20, 0x8b, 0x7c, 0x4d, 0x8a, 0xbd,
|
||||
0x8f, 0x6b, 0x45, 0x62, 0x43, 0xee, 0xc5, 0xb5, 0xc7, 0x6a, 0x8a, 0xf4, 0xa4, 0xd6, 0xe2, 0xcd,
|
||||
0xf8, 0x1d, 0x82, 0x0b, 0x0b, 0xa2, 0xd8, 0x73, 0x0d, 0xbd, 0xf6, 0xc5, 0x3f, 0x3d, 0x5d, 0x46,
|
||||
0x1f, 0x3f, 0x5d, 0x46, 0x7f, 0x7f, 0xba, 0x8c, 0xde, 0xb9, 0x39, 0xd9, 0x3f, 0x43, 0xdc, 0x96,
|
||||
0xcf, 0x82, 0xc4, 0x64, 0xfb, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xaf, 0x09, 0x16, 0xc3, 0xff,
|
||||
0x22, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
@@ -3305,6 +3315,15 @@ func (m *ApplicationQuery) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i -= len(m.XXX_unrecognized)
|
||||
copy(dAtA[i:], m.XXX_unrecognized)
|
||||
}
|
||||
if len(m.Project) > 0 {
|
||||
for iNdEx := len(m.Project) - 1; iNdEx >= 0; iNdEx-- {
|
||||
i -= len(m.Project[iNdEx])
|
||||
copy(dAtA[i:], m.Project[iNdEx])
|
||||
i = encodeVarintApplication(dAtA, i, uint64(len(m.Project[iNdEx])))
|
||||
i--
|
||||
dAtA[i] = 0x42
|
||||
}
|
||||
}
|
||||
if m.Repo != nil {
|
||||
i -= len(*m.Repo)
|
||||
copy(dAtA[i:], *m.Repo)
|
||||
@@ -5105,6 +5124,12 @@ func (m *ApplicationQuery) Size() (n int) {
|
||||
l = len(*m.Repo)
|
||||
n += 1 + l + sovApplication(uint64(l))
|
||||
}
|
||||
if len(m.Project) > 0 {
|
||||
for _, s := range m.Project {
|
||||
l = len(s)
|
||||
n += 1 + l + sovApplication(uint64(l))
|
||||
}
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
}
|
||||
@@ -6092,6 +6117,38 @@ func (m *ApplicationQuery) Unmarshal(dAtA []byte) error {
|
||||
s := string(dAtA[iNdEx:postIndex])
|
||||
m.Repo = &s
|
||||
iNdEx = postIndex
|
||||
case 8:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Project", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowApplication
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthApplication
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthApplication
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Project = append(m.Project, string(dAtA[iNdEx:postIndex]))
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipApplication(dAtA[iNdEx:])
|
||||
|
||||
@@ -18,8 +18,8 @@ const (
|
||||
AppProjectShortName string = "appproject"
|
||||
AppProjectFullName string = AppProjectPlural + "." + Group
|
||||
|
||||
// AppProject constants
|
||||
ApplicationSetKind string = "Applicationset"
|
||||
// ApplicationSet constants
|
||||
ApplicationSetKind string = "ApplicationSet"
|
||||
ApplicationSetSingular string = "applicationset"
|
||||
ApplicationSetPlural string = "applicationsets"
|
||||
ApplicationSetShortName string = "appset"
|
||||
|
||||
@@ -496,7 +496,7 @@ const (
|
||||
// prefix "Info" means informational condition
|
||||
type ApplicationSetConditionType string
|
||||
|
||||
//ErrorOccurred / ParametersGenerated / TemplateRendered / ResourcesUpToDate
|
||||
// ErrorOccurred / ParametersGenerated / TemplateRendered / ResourcesUpToDate
|
||||
const (
|
||||
ApplicationSetConditionErrorOccurred ApplicationSetConditionType = "ErrorOccurred"
|
||||
ApplicationSetConditionParametersGenerated ApplicationSetConditionType = "ParametersGenerated"
|
||||
|
||||
@@ -1241,7 +1241,7 @@ func TestGetAppDetailsKustomize(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "Kustomize", res.Type)
|
||||
assert.NotNil(t, res.Kustomize)
|
||||
assert.EqualValues(t, []string{"nginx:1.15.4", "k8s.gcr.io/nginx-slim:0.8"}, res.Kustomize.Images)
|
||||
assert.EqualValues(t, []string{"nginx:1.15.4", "registry.k8s.io/nginx-slim:0.8"}, res.Kustomize.Images)
|
||||
}
|
||||
|
||||
func TestGetHelmCharts(t *testing.T) {
|
||||
|
||||
@@ -25,7 +25,7 @@ spec:
|
||||
name: daemonset
|
||||
spec:
|
||||
containers:
|
||||
- image: k8s.gcr.io/nginx-slim:0.8
|
||||
- image: registry.k8s.io/nginx-slim:0.8
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: nginx
|
||||
resources: {}
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
name: daemonset
|
||||
spec:
|
||||
containers:
|
||||
- image: k8s.gcr.io/nginx-slim:0.8
|
||||
- image: registry.k8s.io/nginx-slim:0.8
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: nginx
|
||||
resources: {}
|
||||
|
||||
@@ -26,7 +26,7 @@ spec:
|
||||
kubectl.kubernetes.io/restartedAt: "0001-01-01T00:00:00Z"
|
||||
spec:
|
||||
containers:
|
||||
- image: k8s.gcr.io/nginx-slim:0.8
|
||||
- image: registry.k8s.io/nginx-slim:0.8
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: nginx
|
||||
resources: {}
|
||||
|
||||
@@ -24,7 +24,7 @@ spec:
|
||||
app: statefulset
|
||||
spec:
|
||||
containers:
|
||||
- image: k8s.gcr.io/nginx-slim:0.8
|
||||
- image: registry.k8s.io/nginx-slim:0.8
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: nginx
|
||||
resources: {}
|
||||
|
||||
@@ -1,4 +1,55 @@
|
||||
health_status = {}
|
||||
-- Can't use standard lib, math.huge equivalent
|
||||
infinity = 2^1024-1
|
||||
|
||||
local function executor_range_api()
|
||||
min_executor_instances = 0
|
||||
max_executor_instances = infinity
|
||||
if obj.spec.dynamicAllocation.maxExecutors then
|
||||
max_executor_instances = obj.spec.dynamicAllocation.maxExecutors
|
||||
end
|
||||
if obj.spec.dynamicAllocation.minExecutors then
|
||||
min_executor_instances = obj.spec.dynamicAllocation.minExecutors
|
||||
end
|
||||
return min_executor_instances, max_executor_instances
|
||||
end
|
||||
|
||||
local function maybe_executor_range_spark_conf()
|
||||
min_executor_instances = 0
|
||||
max_executor_instances = infinity
|
||||
if obj.spec.sparkConf["spark.streaming.dynamicAllocation.enabled"] ~= nil and
|
||||
obj.spec.sparkConf["spark.streaming.dynamicAllocation.enabled"] == "true" then
|
||||
if(obj.spec.sparkConf["spark.streaming.dynamicAllocation.maxExecutors"] ~= nil) then
|
||||
max_executor_instances = tonumber(obj.spec.sparkConf["spark.streaming.dynamicAllocation.maxExecutors"])
|
||||
end
|
||||
if(obj.spec.sparkConf["spark.streaming.dynamicAllocation.minExecutors"] ~= nil) then
|
||||
min_executor_instances = tonumber(obj.spec.sparkConf["spark.streaming.dynamicAllocation.minExecutors"])
|
||||
end
|
||||
return min_executor_instances, max_executor_instances
|
||||
elseif obj.spec.sparkConf["spark.dynamicAllocation.enabled"] ~= nil and
|
||||
obj.spec.sparkConf["spark.dynamicAllocation.enabled"] == "true" then
|
||||
if(obj.spec.sparkConf["spark.dynamicAllocation.maxExecutors"] ~= nil) then
|
||||
max_executor_instances = tonumber(obj.spec.sparkConf["spark.dynamicAllocation.maxExecutors"])
|
||||
end
|
||||
if(obj.spec.sparkConf["spark.dynamicAllocation.minExecutors"] ~= nil) then
|
||||
min_executor_instances = tonumber(obj.spec.sparkConf["spark.dynamicAllocation.minExecutors"])
|
||||
end
|
||||
return min_executor_instances, max_executor_instances
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function maybe_executor_range()
|
||||
if obj.spec["dynamicAllocation"] and obj.spec.dynamicAllocation.enabled then
|
||||
return executor_range_api()
|
||||
elseif obj.spec["sparkConf"] ~= nil then
|
||||
return maybe_executor_range_spark_conf()
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
if obj.status ~= nil then
|
||||
if obj.status.applicationState.state ~= nil then
|
||||
if obj.status.applicationState.state == "" then
|
||||
@@ -19,6 +70,13 @@ if obj.status ~= nil then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "SparkApplication is Running"
|
||||
return health_status
|
||||
elseif maybe_executor_range() then
|
||||
min_executor_instances, max_executor_instances = maybe_executor_range()
|
||||
if count >= min_executor_instances and count <= max_executor_instances then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "SparkApplication is Running"
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -72,4 +130,4 @@ if obj.status ~= nil then
|
||||
end
|
||||
health_status.status = "Progressing"
|
||||
health_status.message = "Waiting for Executor pods"
|
||||
return health_status
|
||||
return health_status
|
||||
|
||||
@@ -11,3 +11,15 @@ tests:
|
||||
status: Healthy
|
||||
message: "SparkApplication is Running"
|
||||
inputPath: testdata/healthy.yaml
|
||||
- healthStatus:
|
||||
status: Healthy
|
||||
message: "SparkApplication is Running"
|
||||
inputPath: testdata/healthy_dynamic_alloc.yaml
|
||||
- healthStatus:
|
||||
status: Healthy
|
||||
message: "SparkApplication is Running"
|
||||
inputPath: testdata/healthy_dynamic_alloc_dstream.yaml
|
||||
- healthStatus:
|
||||
status: Healthy
|
||||
message: "SparkApplication is Running"
|
||||
inputPath: testdata/healthy_dynamic_alloc_operator_api.yaml
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
apiVersion: sparkoperator.k8s.io/v1beta2
|
||||
kind: SparkApplication
|
||||
metadata:
|
||||
generation: 4
|
||||
labels:
|
||||
argocd.argoproj.io/instance: spark-job
|
||||
name: spark-job-app
|
||||
namespace: spark-cluster
|
||||
resourceVersion: "31812990"
|
||||
uid: bfee52b0-74ca-4465-8005-f6643097ed64
|
||||
spec:
|
||||
executor:
|
||||
instances: 4
|
||||
sparkConf:
|
||||
spark.dynamicAllocation.enabled: 'true'
|
||||
spark.dynamicAllocation.maxExecutors: '10'
|
||||
spark.dynamicAllocation.minExecutors: '2'
|
||||
status:
|
||||
applicationState:
|
||||
state: RUNNING
|
||||
driverInfo:
|
||||
podName: ingestion-datalake-news-app-driver
|
||||
webUIAddress: 172.20.207.161:4040
|
||||
webUIPort: 4040
|
||||
webUIServiceName: ingestion-datalake-news-app-ui-svc
|
||||
executionAttempts: 13
|
||||
executorState:
|
||||
ingestion-datalake-news-app-1591613851251-exec-1: RUNNING
|
||||
ingestion-datalake-news-app-1591613851251-exec-2: RUNNING
|
||||
ingestion-datalake-news-app-1591613851251-exec-4: RUNNING
|
||||
ingestion-datalake-news-app-1591613851251-exec-5: RUNNING
|
||||
ingestion-datalake-news-app-1591613851251-exec-6: RUNNING
|
||||
lastSubmissionAttemptTime: "2020-06-08T10:57:32Z"
|
||||
sparkApplicationId: spark-a5920b2a5aa04d22a737c60759b5bf82
|
||||
submissionAttempts: 1
|
||||
submissionID: 3e713ec8-9f6c-4e78-ac28-749797c846f0
|
||||
terminationTime: null
|
||||
@@ -0,0 +1,35 @@
|
||||
apiVersion: sparkoperator.k8s.io/v1beta2
|
||||
kind: SparkApplication
|
||||
metadata:
|
||||
generation: 4
|
||||
labels:
|
||||
argocd.argoproj.io/instance: spark-job
|
||||
name: spark-job-app
|
||||
namespace: spark-cluster
|
||||
resourceVersion: "31812990"
|
||||
uid: bfee52b0-74ca-4465-8005-f6643097ed64
|
||||
spec:
|
||||
executor:
|
||||
instances: 4
|
||||
sparkConf:
|
||||
spark.streaming.dynamicAllocation.enabled: 'true'
|
||||
spark.streaming.dynamicAllocation.maxExecutors: '10'
|
||||
spark.streaming.dynamicAllocation.minExecutors: '2'
|
||||
status:
|
||||
applicationState:
|
||||
state: RUNNING
|
||||
driverInfo:
|
||||
podName: ingestion-datalake-news-app-driver
|
||||
webUIAddress: 172.20.207.161:4040
|
||||
webUIPort: 4040
|
||||
webUIServiceName: ingestion-datalake-news-app-ui-svc
|
||||
executionAttempts: 13
|
||||
executorState:
|
||||
ingestion-datalake-news-app-1591613851251-exec-1: RUNNING
|
||||
ingestion-datalake-news-app-1591613851251-exec-4: RUNNING
|
||||
ingestion-datalake-news-app-1591613851251-exec-6: RUNNING
|
||||
lastSubmissionAttemptTime: "2020-06-08T10:57:32Z"
|
||||
sparkApplicationId: spark-a5920b2a5aa04d22a737c60759b5bf82
|
||||
submissionAttempts: 1
|
||||
submissionID: 3e713ec8-9f6c-4e78-ac28-749797c846f0
|
||||
terminationTime: null
|
||||
@@ -0,0 +1,38 @@
|
||||
apiVersion: sparkoperator.k8s.io/v1beta2
|
||||
kind: SparkApplication
|
||||
metadata:
|
||||
generation: 4
|
||||
labels:
|
||||
argocd.argoproj.io/instance: spark-job
|
||||
name: spark-job-app
|
||||
namespace: spark-cluster
|
||||
resourceVersion: "31812990"
|
||||
uid: bfee52b0-74ca-4465-8005-f6643097ed64
|
||||
spec:
|
||||
executor:
|
||||
instances: 4
|
||||
dynamicAllocation:
|
||||
enabled: true
|
||||
initialExecutors: 2
|
||||
minExecutors: 2
|
||||
maxExecutors: 10
|
||||
status:
|
||||
applicationState:
|
||||
state: RUNNING
|
||||
driverInfo:
|
||||
podName: ingestion-datalake-news-app-driver
|
||||
webUIAddress: 172.20.207.161:4040
|
||||
webUIPort: 4040
|
||||
webUIServiceName: ingestion-datalake-news-app-ui-svc
|
||||
executionAttempts: 13
|
||||
executorState:
|
||||
ingestion-datalake-news-app-1591613851251-exec-1: RUNNING
|
||||
ingestion-datalake-news-app-1591613851251-exec-2: RUNNING
|
||||
ingestion-datalake-news-app-1591613851251-exec-4: RUNNING
|
||||
ingestion-datalake-news-app-1591613851251-exec-5: RUNNING
|
||||
ingestion-datalake-news-app-1591613851251-exec-6: RUNNING
|
||||
lastSubmissionAttemptTime: "2020-06-08T10:57:32Z"
|
||||
sparkApplicationId: spark-a5920b2a5aa04d22a737c60759b5bf82
|
||||
submissionAttempts: 1
|
||||
submissionID: 3e713ec8-9f6c-4e78-ac28-749797c846f0
|
||||
terminationTime: null
|
||||
@@ -67,7 +67,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
watchAPIBufferSize = env.ParseNumFromEnv(argocommon.EnvWatchAPIBufferSize, 1000, 0, math.MaxInt32)
|
||||
watchAPIBufferSize = env.ParseNumFromEnv(argocommon.EnvWatchAPIBufferSize, 1000, 0, math.MaxInt32)
|
||||
permissionDeniedErr = status.Error(codes.PermissionDenied, "permission denied")
|
||||
)
|
||||
|
||||
// Server provides an Application service
|
||||
@@ -77,7 +78,7 @@ type Server struct {
|
||||
appclientset appclientset.Interface
|
||||
appLister applisters.ApplicationNamespaceLister
|
||||
appInformer cache.SharedIndexInformer
|
||||
appBroadcaster *broadcasterHandler
|
||||
appBroadcaster Broadcaster
|
||||
repoClientset apiclient.Clientset
|
||||
kubectl kube.Kubectl
|
||||
db db.ArgoDB
|
||||
@@ -96,6 +97,7 @@ func NewServer(
|
||||
appclientset appclientset.Interface,
|
||||
appLister applisters.ApplicationNamespaceLister,
|
||||
appInformer cache.SharedIndexInformer,
|
||||
appBroadcaster Broadcaster,
|
||||
repoClientset apiclient.Clientset,
|
||||
cache *servercache.Cache,
|
||||
kubectl kube.Kubectl,
|
||||
@@ -105,7 +107,9 @@ func NewServer(
|
||||
settingsMgr *settings.SettingsManager,
|
||||
projInformer cache.SharedIndexInformer,
|
||||
) (application.ApplicationServiceServer, AppResourceTreeFn) {
|
||||
appBroadcaster := &broadcasterHandler{}
|
||||
if appBroadcaster == nil {
|
||||
appBroadcaster = &broadcasterHandler{}
|
||||
}
|
||||
appInformer.AddEventHandler(appBroadcaster)
|
||||
s := &Server{
|
||||
ns: namespace,
|
||||
@@ -127,6 +131,57 @@ func NewServer(
|
||||
return s, s.GetAppResources
|
||||
}
|
||||
|
||||
// getAppEnforceRBAC gets the Application with the given name in the given namespace. If no namespace is
|
||||
// specified, the Application is fetched from the default namespace (the one in which the API server is running).
|
||||
//
|
||||
// If the Application does not exist, then we have no way of determining if the user would have had access to get that
|
||||
// Application. Verifying access requires knowing the Application's name, namespace, and project. The user may specify,
|
||||
// at minimum, the Application name.
|
||||
//
|
||||
// So to prevent a malicious user from inferring the existence or absense of the Application or namespace, we respond
|
||||
// "permission denied" if the Application does not exist.
|
||||
func (s *Server) getAppEnforceRBAC(ctx context.Context, action, name string, getApp func() (*appv1.Application, error)) (*appv1.Application, error) {
|
||||
logCtx := log.WithFields(map[string]interface{}{
|
||||
"application": name,
|
||||
})
|
||||
a, err := getApp()
|
||||
if err != nil {
|
||||
if apierr.IsNotFound(err) {
|
||||
logCtx.Warn("application does not exist")
|
||||
return nil, permissionDeniedErr
|
||||
}
|
||||
logCtx.Errorf("failed to get application: %s", err)
|
||||
return nil, permissionDeniedErr
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, action, apputil.AppRBACName(*a)); err != nil {
|
||||
logCtx.WithFields(map[string]interface{}{
|
||||
"project": a.Spec.Project,
|
||||
}).Warnf("user tried to %s application which they do not have access to: %s", action, err)
|
||||
return nil, permissionDeniedErr
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// getApplicationEnforceRBACInformer uses an informer to get an Application. If the app does not exist, permission is
|
||||
// denied, or any other error occurs when getting the app, we return a permission denied error to obscure any sensitive
|
||||
// information.
|
||||
func (s *Server) getApplicationEnforceRBACInformer(ctx context.Context, action, name string) (*appv1.Application, error) {
|
||||
return s.getAppEnforceRBAC(ctx, action, name, func() (*appv1.Application, error) {
|
||||
return s.appLister.Get(name)
|
||||
})
|
||||
}
|
||||
|
||||
// getApplicationEnforceRBACClient uses a client to get an Application. If the app does not exist, permission is denied,
|
||||
// or any other error occurs when getting the app, we return a permission denied error to obscure any sensitive
|
||||
// information.
|
||||
func (s *Server) getApplicationEnforceRBACClient(ctx context.Context, action, name, resourceVersion string) (*appv1.Application, error) {
|
||||
return s.getAppEnforceRBAC(ctx, action, name, func() (*appv1.Application, error) {
|
||||
return s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, name, metav1.GetOptions{
|
||||
ResourceVersion: resourceVersion,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// List returns list of applications
|
||||
func (s *Server) List(ctx context.Context, q *application.ApplicationQuery) (*appv1.ApplicationList, error) {
|
||||
labelsMap, err := labels.ConvertSelectorToLabelsMap(q.GetSelector())
|
||||
@@ -150,8 +205,8 @@ func (s *Server) List(ctx context.Context, q *application.ApplicationQuery) (*ap
|
||||
}
|
||||
}
|
||||
|
||||
// Filter applications by name
|
||||
newItems = argoutil.FilterByProjects(newItems, q.Projects)
|
||||
// Filter applications by projects
|
||||
newItems = argoutil.FilterByProjects(newItems, getProjectsFromApplicationQuery(*q))
|
||||
|
||||
// Filter applications by source repo URL
|
||||
newItems = argoutil.FilterByRepo(newItems, q.GetRepo())
|
||||
@@ -291,11 +346,11 @@ func (s *Server) queryRepoServer(ctx context.Context, a *v1alpha1.Application, a
|
||||
|
||||
// GetManifests returns application manifests
|
||||
func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationManifestQuery) (*apiclient.ManifestResponse, error) {
|
||||
a, err := s.appLister.Get(*q.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application: %w", err)
|
||||
if q.Name == nil || *q.Name == "" {
|
||||
return nil, fmt.Errorf("invalid request: application name is missing")
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -381,17 +436,13 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
|
||||
|
||||
// Get returns an application by name
|
||||
func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*appv1.Application, error) {
|
||||
appName := q.GetName()
|
||||
|
||||
// We must use a client Get instead of an informer Get, because it's common to call Get immediately
|
||||
// following a Watch (which is not yet powered by an informer), and the Get must reflect what was
|
||||
// previously seen by the client.
|
||||
a, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, q.GetName(), metav1.GetOptions{
|
||||
ResourceVersion: q.GetResourceVersion(),
|
||||
})
|
||||
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, appName, q.GetResourceVersion())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if q.Refresh == nil {
|
||||
@@ -469,11 +520,8 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
|
||||
|
||||
// ListResourceEvents returns a list of event resources
|
||||
func (s *Server) ListResourceEvents(ctx context.Context, q *application.ApplicationResourceEventsQuery) (*v1.EventList, error) {
|
||||
a, err := s.appLister.Get(*q.Name)
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetName())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
@@ -533,13 +581,13 @@ func (s *Server) ListResourceEvents(ctx context.Context, q *application.Applicat
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (s *Server) validateAndUpdateApp(ctx context.Context, newApp *appv1.Application, merge bool, validate bool) (*appv1.Application, error) {
|
||||
func (s *Server) validateAndUpdateApp(ctx context.Context, newApp *appv1.Application, merge bool, validate bool, action string) (*appv1.Application, error) {
|
||||
s.projectLock.RLock(newApp.Spec.GetProject())
|
||||
defer s.projectLock.RUnlock(newApp.Spec.GetProject())
|
||||
|
||||
app, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, newApp.Name, metav1.GetOptions{})
|
||||
app, err := s.getApplicationEnforceRBACClient(ctx, action, newApp.Name, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.validateAndNormalizeApp(ctx, newApp, validate)
|
||||
@@ -641,7 +689,7 @@ func (s *Server) Update(ctx context.Context, q *application.ApplicationUpdateReq
|
||||
if q.Validate != nil {
|
||||
validate = *q.Validate
|
||||
}
|
||||
return s.validateAndUpdateApp(ctx, q.Application, false, validate)
|
||||
return s.validateAndUpdateApp(ctx, q.Application, false, validate, rbacpolicy.ActionUpdate)
|
||||
}
|
||||
|
||||
// UpdateSpec updates an application spec and filters out any invalid parameter overrides
|
||||
@@ -649,11 +697,8 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
|
||||
if q.GetSpec() == nil {
|
||||
return nil, fmt.Errorf("error updating application spec: spec is nil in request")
|
||||
}
|
||||
a, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, *q.Name, metav1.GetOptions{})
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionUpdate, q.GetName(), "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, apputil.AppRBACName(*a)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.Spec = *q.GetSpec()
|
||||
@@ -661,7 +706,7 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
|
||||
if q.Validate != nil {
|
||||
validate = *q.Validate
|
||||
}
|
||||
a, err = s.validateAndUpdateApp(ctx, a, false, validate)
|
||||
a, err = s.validateAndUpdateApp(ctx, a, false, validate, rbacpolicy.ActionUpdate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error validating and updating app: %w", err)
|
||||
}
|
||||
@@ -670,10 +715,9 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
|
||||
|
||||
// Patch patches an application
|
||||
func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchRequest) (*appv1.Application, error) {
|
||||
|
||||
app, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, *q.Name, metav1.GetOptions{})
|
||||
app, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetName(), "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, apputil.AppRBACName(*app)); err != nil {
|
||||
@@ -711,14 +755,15 @@ func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchReque
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling patched app: %w", err)
|
||||
}
|
||||
return s.validateAndUpdateApp(ctx, newApp, false, true)
|
||||
return s.validateAndUpdateApp(ctx, newApp, false, true, rbacpolicy.ActionUpdate)
|
||||
}
|
||||
|
||||
// Delete removes an application and all associated resources
|
||||
func (s *Server) Delete(ctx context.Context, q *application.ApplicationDeleteRequest) (*application.ApplicationResponse, error) {
|
||||
a, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, *q.Name, metav1.GetOptions{})
|
||||
appName := q.GetName()
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, appName, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.projectLock.RLock(a.Spec.Project)
|
||||
@@ -782,8 +827,8 @@ func (s *Server) Watch(q *application.ApplicationQuery, ws application.Applicati
|
||||
logCtx = logCtx.WithField("application", *q.Name)
|
||||
}
|
||||
projects := map[string]bool{}
|
||||
for i := range q.Projects {
|
||||
projects[q.Projects[i]] = true
|
||||
for _, project := range getProjectsFromApplicationQuery(*q) {
|
||||
projects[project] = true
|
||||
}
|
||||
claims := ws.Context().Value("claims")
|
||||
selector, err := labels.Parse(q.GetSelector())
|
||||
@@ -859,7 +904,9 @@ func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Applica
|
||||
proj, err := argo.GetAppProject(&app.Spec, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
|
||||
if err != nil {
|
||||
if apierr.IsNotFound(err) {
|
||||
return status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", app.Spec.Project)
|
||||
// Offer no hint that the project does not exist.
|
||||
log.Warnf("User attempted to create/update application in non-existent project %q", app.Spec.Project)
|
||||
return permissionDeniedErr
|
||||
}
|
||||
return fmt.Errorf("error getting application's project: %w", err)
|
||||
}
|
||||
@@ -966,20 +1013,16 @@ func (s *Server) GetAppResources(ctx context.Context, a *appv1.Application) (*ap
|
||||
return s.cache.GetAppResourcesTree(a.Name, &tree)
|
||||
})
|
||||
if err != nil {
|
||||
return &tree, fmt.Errorf("error getting cached app state: %w", err)
|
||||
return &tree, fmt.Errorf("error getting cached app resource tree: %w", err)
|
||||
}
|
||||
return &tree, nil
|
||||
}
|
||||
|
||||
func (s *Server) getAppLiveResource(ctx context.Context, action string, q *application.ApplicationResourceRequest) (*appv1.ResourceNode, *rest.Config, *appv1.Application, error) {
|
||||
a, err := s.appLister.Get(*q.Name)
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, action, q.GetName())
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("error getting app by name: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, action, apputil.AppRBACName(*a)); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
tree, err := s.GetAppResources(ctx, a)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("error getting app resources: %w", err)
|
||||
@@ -999,7 +1042,7 @@ func (s *Server) getAppLiveResource(ctx context.Context, action string, q *appli
|
||||
func (s *Server) GetResource(ctx context.Context, q *application.ApplicationResourceRequest) (*application.ApplicationResourceResponse, error) {
|
||||
res, config, _, err := s.getAppLiveResource(ctx, rbacpolicy.ActionGet, q)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting app live resource: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// make sure to use specified resource version if provided
|
||||
@@ -1045,9 +1088,6 @@ func (s *Server) PatchResource(ctx context.Context, q *application.ApplicationRe
|
||||
}
|
||||
res, config, a, err := s.getAppLiveResource(ctx, rbacpolicy.ActionUpdate, resourceRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting app live resource: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, apputil.AppRBACName(*a)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1059,6 +1099,9 @@ func (s *Server) PatchResource(ctx context.Context, q *application.ApplicationRe
|
||||
}
|
||||
return nil, fmt.Errorf("error patching resource: %w", err)
|
||||
}
|
||||
if manifest == nil {
|
||||
return nil, fmt.Errorf("failed to patch resource: manifest was nil")
|
||||
}
|
||||
manifest, err = replaceSecretValues(manifest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error replacing secret values: %w", err)
|
||||
@@ -1086,9 +1129,6 @@ func (s *Server) DeleteResource(ctx context.Context, q *application.ApplicationR
|
||||
}
|
||||
res, config, a, err := s.getAppLiveResource(ctx, rbacpolicy.ActionDelete, resourceRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting live resource for delete: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionDelete, apputil.AppRBACName(*a)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var deleteOption metav1.DeleteOptions
|
||||
@@ -1112,23 +1152,16 @@ func (s *Server) DeleteResource(ctx context.Context, q *application.ApplicationR
|
||||
}
|
||||
|
||||
func (s *Server) ResourceTree(ctx context.Context, q *application.ResourcesQuery) (*appv1.ApplicationTree, error) {
|
||||
a, err := s.appLister.Get(q.GetApplicationName())
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetApplicationName())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application by name: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.GetAppResources(ctx, a)
|
||||
}
|
||||
|
||||
func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application.ApplicationService_WatchResourceTreeServer) error {
|
||||
a, err := s.appLister.Get(q.GetApplicationName())
|
||||
_, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetApplicationName())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting application by name: %w", err)
|
||||
}
|
||||
|
||||
if err := s.enf.EnforceErr(ws.Context().Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1143,11 +1176,8 @@ func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application
|
||||
}
|
||||
|
||||
func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMetadataQuery) (*v1alpha1.RevisionMetadata, error) {
|
||||
a, err := s.appLister.Get(q.GetName())
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetName())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting app by name: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repo, err := s.db.GetRepository(ctx, a.Spec.Source.RepoURL)
|
||||
@@ -1180,19 +1210,16 @@ func isMatchingResource(q *application.ResourcesQuery, key kube.ResourceKey) boo
|
||||
}
|
||||
|
||||
func (s *Server) ManagedResources(ctx context.Context, q *application.ResourcesQuery) (*application.ManagedResourcesResponse, error) {
|
||||
a, err := s.appLister.Get(*q.ApplicationName)
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetApplicationName())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
|
||||
return nil, fmt.Errorf("error verifying rbac: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
items := make([]*appv1.ResourceDiff, 0)
|
||||
err = s.getCachedAppState(ctx, a, func() error {
|
||||
return s.cache.GetAppManagedResources(a.Name, &items)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting cached app state: %w", err)
|
||||
return nil, fmt.Errorf("error getting cached app managed resources: %w", err)
|
||||
}
|
||||
res := &application.ManagedResourcesResponse{}
|
||||
for i := range items {
|
||||
@@ -1239,12 +1266,8 @@ func (s *Server) PodLogs(q *application.ApplicationPodLogsQuery, ws application.
|
||||
}
|
||||
}
|
||||
|
||||
a, err := s.appLister.Get(q.GetName())
|
||||
a, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetName())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting application by name: %w", err)
|
||||
}
|
||||
|
||||
if err := s.enf.EnforceErr(ws.Context().Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1436,10 +1459,9 @@ func isTheSelectedOne(currentNode *appv1.ResourceNode, q *application.Applicatio
|
||||
|
||||
// Sync syncs an application to its target state
|
||||
func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncRequest) (*appv1.Application, error) {
|
||||
appIf := s.appclientset.ArgoprojV1alpha1().Applications(s.ns)
|
||||
a, err := appIf.Get(ctx, *syncReq.Name, metav1.GetOptions{})
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, syncReq.GetName(), "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application by name: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proj, err := argo.GetAppProject(&a.Spec, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), a.Namespace, s.settingsMgr, s.db, ctx)
|
||||
@@ -1521,7 +1543,9 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
|
||||
op.Retry = *retry
|
||||
}
|
||||
|
||||
a, err = argo.SetAppOperation(appIf, *syncReq.Name, &op)
|
||||
appName := syncReq.GetName()
|
||||
appIf := s.appclientset.ArgoprojV1alpha1().Applications(s.ns)
|
||||
a, err = argo.SetAppOperation(appIf, appName, &op)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error setting app operation: %w", err)
|
||||
}
|
||||
@@ -1538,12 +1562,8 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
|
||||
}
|
||||
|
||||
func (s *Server) Rollback(ctx context.Context, rollbackReq *application.ApplicationRollbackRequest) (*appv1.Application, error) {
|
||||
appIf := s.appclientset.ArgoprojV1alpha1().Applications(s.ns)
|
||||
a, err := appIf.Get(ctx, *rollbackReq.Name, metav1.GetOptions{})
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, rollbackReq.GetName(), "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application by name: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionSync, apputil.AppRBACName(*a)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if a.DeletionTimestamp != nil {
|
||||
@@ -1585,7 +1605,9 @@ func (s *Server) Rollback(ctx context.Context, rollbackReq *application.Applicat
|
||||
Source: &deploymentInfo.Source,
|
||||
},
|
||||
}
|
||||
a, err = argo.SetAppOperation(appIf, *rollbackReq.Name, &op)
|
||||
appName := rollbackReq.GetName()
|
||||
appIf := s.appclientset.ArgoprojV1alpha1().Applications(s.ns)
|
||||
a, err = argo.SetAppOperation(appIf, appName, &op)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error setting app operation: %w", err)
|
||||
}
|
||||
@@ -1632,11 +1654,9 @@ func (s *Server) resolveRevision(ctx context.Context, app *appv1.Application, sy
|
||||
}
|
||||
|
||||
func (s *Server) TerminateOperation(ctx context.Context, termOpReq *application.OperationTerminateRequest) (*application.OperationTerminateResponse, error) {
|
||||
a, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, *termOpReq.Name, metav1.GetOptions{})
|
||||
appName := termOpReq.GetName()
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, appName, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application by name: %w", err)
|
||||
}
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionSync, apputil.AppRBACName(*a)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1687,7 +1707,7 @@ func (s *Server) logResourceEvent(res *appv1.ResourceNode, ctx context.Context,
|
||||
func (s *Server) ListResourceActions(ctx context.Context, q *application.ApplicationResourceRequest) (*application.ResourceActionsListResponse, error) {
|
||||
res, config, _, err := s.getAppLiveResource(ctx, rbacpolicy.ActionGet, q)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting app live resource: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
obj, err := s.kubectl.GetResource(ctx, config, res.GroupKindVersion(), res.Name, res.Namespace)
|
||||
if err != nil {
|
||||
@@ -1742,7 +1762,7 @@ func (s *Server) RunResourceAction(ctx context.Context, q *application.ResourceA
|
||||
actionRequest := fmt.Sprintf("%s/%s/%s/%s", rbacpolicy.ActionAction, q.GetGroup(), q.GetKind(), q.GetAction())
|
||||
res, config, a, err := s.getAppLiveResource(ctx, actionRequest, resourceRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting app live resource: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
liveObj, err := s.kubectl.GetResource(ctx, config, res.GroupKindVersion(), res.Name, res.Namespace)
|
||||
if err != nil {
|
||||
@@ -1869,13 +1889,8 @@ func (s *Server) plugins() ([]*v1alpha1.ConfigManagementPlugin, error) {
|
||||
}
|
||||
|
||||
func (s *Server) GetApplicationSyncWindows(ctx context.Context, q *application.ApplicationSyncWindowsQuery) (*application.ApplicationSyncWindowsResponse, error) {
|
||||
appIf := s.appclientset.ArgoprojV1alpha1().Applications(s.ns)
|
||||
a, err := appIf.Get(ctx, *q.Name, metav1.GetOptions{})
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetName(), "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application by name: %w", err)
|
||||
}
|
||||
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1927,3 +1942,12 @@ func getPropagationPolicyFinalizer(policy string) string {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// getProjectFromApplicationQuery gets the project names from a query. If the legacy "project" field was specified, use
|
||||
// that. Otherwise, use the newer "projects" field.
|
||||
func getProjectsFromApplicationQuery(q application.ApplicationQuery) []string {
|
||||
if q.Project != nil {
|
||||
return q.Project
|
||||
}
|
||||
return q.Projects
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ message ApplicationQuery {
|
||||
optional string selector = 5;
|
||||
// the repoURL to restrict returned list applications
|
||||
optional string repo = 6;
|
||||
// the project names to restrict returned list applications (legacy name for backwards-compatibility)
|
||||
repeated string project = 8;
|
||||
}
|
||||
|
||||
message NodeQuery {
|
||||
|
||||
@@ -4,11 +4,13 @@ import (
|
||||
"context"
|
||||
coreerrors "errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
synccommon "github.com/argoproj/gitops-engine/pkg/sync/common"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube/kubetest"
|
||||
"github.com/argoproj/pkg/sync"
|
||||
"github.com/ghodss/yaml"
|
||||
@@ -17,13 +19,17 @@ import (
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
k8sappsv1 "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
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/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
"k8s.io/client-go/rest"
|
||||
kubetesting "k8s.io/client-go/testing"
|
||||
k8scache "k8s.io/client-go/tools/cache"
|
||||
"k8s.io/utils/pointer"
|
||||
@@ -35,10 +41,13 @@ import (
|
||||
appinformer "github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
|
||||
appmocks "github.com/argoproj/argo-cd/v2/server/application/mocks"
|
||||
servercache "github.com/argoproj/argo-cd/v2/server/cache"
|
||||
"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
|
||||
"github.com/argoproj/argo-cd/v2/test"
|
||||
"github.com/argoproj/argo-cd/v2/util/assets"
|
||||
"github.com/argoproj/argo-cd/v2/util/cache"
|
||||
"github.com/argoproj/argo-cd/v2/util/cache/appstate"
|
||||
"github.com/argoproj/argo-cd/v2/util/db"
|
||||
"github.com/argoproj/argo-cd/v2/util/errors"
|
||||
"github.com/argoproj/argo-cd/v2/util/grpc"
|
||||
@@ -93,6 +102,7 @@ func fakeRepoServerClient(isHelm bool) *mocks.RepoServerServiceClient {
|
||||
mockRepoServiceClient.On("GenerateManifest", mock.Anything, mock.Anything).Return(&apiclient.ManifestResponse{}, nil)
|
||||
mockRepoServiceClient.On("GetAppDetails", mock.Anything, mock.Anything).Return(&apiclient.RepoAppDetailsResponse{}, nil)
|
||||
mockRepoServiceClient.On("TestRepository", mock.Anything, mock.Anything).Return(&apiclient.TestRepositoryResponse{}, nil)
|
||||
mockRepoServiceClient.On("GetRevisionMetadata", mock.Anything, mock.Anything).Return(&appsv1.RevisionMetadata{}, nil)
|
||||
|
||||
if isHelm {
|
||||
mockRepoServiceClient.On("ResolveRevision", mock.Anything, mock.Anything).Return(fakeResolveRevesionResponseHelm(), nil)
|
||||
@@ -104,15 +114,15 @@ func fakeRepoServerClient(isHelm bool) *mocks.RepoServerServiceClient {
|
||||
}
|
||||
|
||||
// return an ApplicationServiceServer which returns fake data
|
||||
func newTestAppServer(objects ...runtime.Object) *Server {
|
||||
func newTestAppServer(t *testing.T, objects ...runtime.Object) *Server {
|
||||
f := func(enf *rbac.Enforcer) {
|
||||
_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
|
||||
enf.SetDefaultRole("role:admin")
|
||||
}
|
||||
return newTestAppServerWithEnforcerConfigure(f, objects...)
|
||||
return newTestAppServerWithEnforcerConfigure(f, t, objects...)
|
||||
}
|
||||
|
||||
func newTestAppServerWithEnforcerConfigure(f func(*rbac.Enforcer), objects ...runtime.Object) *Server {
|
||||
func newTestAppServerWithEnforcerConfigure(f func(*rbac.Enforcer), t *testing.T, objects ...runtime.Object) *Server {
|
||||
kubeclientset := fake.NewSimpleClientset(&v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: testNamespace,
|
||||
@@ -197,15 +207,83 @@ func newTestAppServerWithEnforcerConfigure(f func(*rbac.Enforcer), objects ...ru
|
||||
panic("Timed out waiting for caches to sync")
|
||||
}
|
||||
|
||||
broadcaster := new(appmocks.Broadcaster)
|
||||
broadcaster.On("Subscribe", mock.Anything, mock.Anything).Return(func() {}).Run(func(args mock.Arguments) {
|
||||
// Simulate the broadcaster notifying the subscriber of an application update.
|
||||
// The second parameter to Subscribe is filters. For the purposes of tests, we ignore the filters. Future tests
|
||||
// might require implementing those.
|
||||
go func() {
|
||||
events := args.Get(0).(chan *appsv1.ApplicationWatchEvent)
|
||||
for _, obj := range objects {
|
||||
app, ok := obj.(*appsv1.Application)
|
||||
if ok {
|
||||
oldVersion, err := strconv.Atoi(app.ResourceVersion)
|
||||
if err != nil {
|
||||
oldVersion = 0
|
||||
}
|
||||
clonedApp := app.DeepCopy()
|
||||
clonedApp.ResourceVersion = fmt.Sprintf("%d", oldVersion+1)
|
||||
events <- &appsv1.ApplicationWatchEvent{Type: watch.Added, Application: *clonedApp}
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
broadcaster.On("OnAdd", mock.Anything).Return()
|
||||
broadcaster.On("OnUpdate", mock.Anything, mock.Anything).Return()
|
||||
broadcaster.On("OnDelete", mock.Anything).Return()
|
||||
|
||||
appStateCache := appstate.NewCache(cache.NewCache(cache.NewInMemoryCache(time.Hour)), time.Hour)
|
||||
// pre-populate the app cache
|
||||
for _, obj := range objects {
|
||||
app, ok := obj.(*appsv1.Application)
|
||||
if ok {
|
||||
err := appStateCache.SetAppManagedResources(app.Name, []*appsv1.ResourceDiff{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Pre-populate the resource tree based on the app's resources.
|
||||
nodes := make([]appsv1.ResourceNode, len(app.Status.Resources))
|
||||
for i, res := range app.Status.Resources {
|
||||
nodes[i] = appsv1.ResourceNode{
|
||||
ResourceRef: appsv1.ResourceRef{
|
||||
Group: res.Group,
|
||||
Kind: res.Kind,
|
||||
Version: res.Version,
|
||||
Name: res.Name,
|
||||
Namespace: res.Namespace,
|
||||
UID: "fake",
|
||||
},
|
||||
}
|
||||
}
|
||||
err = appStateCache.SetAppResourcesTree(app.Name, &appsv1.ApplicationTree{
|
||||
Nodes: nodes,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
appCache := servercache.NewCache(appStateCache, time.Hour, time.Hour, time.Hour)
|
||||
|
||||
kubectl := &kubetest.MockKubectlCmd{}
|
||||
kubectl = kubectl.WithGetResourceFunc(func(_ context.Context, _ *rest.Config, gvk schema.GroupVersionKind, name string, namespace string) (*unstructured.Unstructured, error) {
|
||||
for _, obj := range objects {
|
||||
if obj.GetObjectKind().GroupVersionKind().GroupKind() == gvk.GroupKind() {
|
||||
if obj, ok := obj.(*unstructured.Unstructured); ok && obj.GetName() == name && obj.GetNamespace() == namespace {
|
||||
return obj, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
server, _ := NewServer(
|
||||
testNamespace,
|
||||
kubeclientset,
|
||||
fakeAppsClientset,
|
||||
factory.Argoproj().V1alpha1().Applications().Lister().Applications(testNamespace),
|
||||
appInformer,
|
||||
broadcaster,
|
||||
mockRepoClient,
|
||||
nil,
|
||||
&kubetest.MockKubectlCmd{},
|
||||
appCache,
|
||||
kubectl,
|
||||
db,
|
||||
enforcer,
|
||||
sync.NewKeyLock(),
|
||||
@@ -295,8 +373,462 @@ func createTestApp(testApp string, opts ...func(app *appsv1.Application)) *appsv
|
||||
return &app
|
||||
}
|
||||
|
||||
type TestResourceTreeServer struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (t *TestResourceTreeServer) Send(tree *appsv1.ApplicationTree) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestResourceTreeServer) SetHeader(metadata.MD) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestResourceTreeServer) SendHeader(metadata.MD) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestResourceTreeServer) SetTrailer(metadata.MD) {}
|
||||
|
||||
func (t *TestResourceTreeServer) Context() context.Context {
|
||||
return t.ctx
|
||||
}
|
||||
|
||||
func (t *TestResourceTreeServer) SendMsg(m interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestResourceTreeServer) RecvMsg(m interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type TestPodLogsServer struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (t *TestPodLogsServer) Send(log *application.LogEntry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestPodLogsServer) SetHeader(metadata.MD) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestPodLogsServer) SendHeader(metadata.MD) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestPodLogsServer) SetTrailer(metadata.MD) {}
|
||||
|
||||
func (t *TestPodLogsServer) Context() context.Context {
|
||||
return t.ctx
|
||||
}
|
||||
|
||||
func (t *TestPodLogsServer) SendMsg(m interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestPodLogsServer) RecvMsg(m interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestNoAppEnumeration(t *testing.T) {
|
||||
// This test ensures that malicious users can't infer the existence or non-existence of Applications by inspecting
|
||||
// error messages. The errors for "app does not exist" must be the same as errors for "you aren't allowed to
|
||||
// interact with this app."
|
||||
|
||||
// These tests are only important on API calls where the full app RBAC name (project, namespace, and name) is _not_
|
||||
// known based on the query parameters. For example, the Create call cannot leak existence of Applications, because
|
||||
// the Application's project, namespace, and name are all specified in the API call. The call can be rejected
|
||||
// immediately if the user does not have access. But the Delete endpoint may be called with just the Application
|
||||
// name. So we cannot return a different error message for "does not exist" and "you don't have delete permissions,"
|
||||
// because the user could infer that the Application exists if they do not get the "does not exist" message. For
|
||||
// endpoints that do not require the full RBAC name, we must return a generic "permission denied" for both "does not
|
||||
// exist" and "no access."
|
||||
|
||||
f := func(enf *rbac.Enforcer) {
|
||||
_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
|
||||
enf.SetDefaultRole("role:none")
|
||||
}
|
||||
deployment := k8sappsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
Namespace: "test",
|
||||
},
|
||||
}
|
||||
testApp := newTestApp(func(app *appsv1.Application) {
|
||||
app.Name = "test"
|
||||
app.Status.Resources = []appsv1.ResourceStatus{
|
||||
{
|
||||
Group: deployment.GroupVersionKind().Group,
|
||||
Kind: deployment.GroupVersionKind().Kind,
|
||||
Version: deployment.GroupVersionKind().Version,
|
||||
Name: deployment.Name,
|
||||
Namespace: deployment.Namespace,
|
||||
Status: "Synced",
|
||||
},
|
||||
}
|
||||
app.Status.History = []appsv1.RevisionHistory{
|
||||
{
|
||||
ID: 0,
|
||||
Source: appsv1.ApplicationSource{
|
||||
TargetRevision: "something-old",
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
testDeployment := kube.MustToUnstructured(&deployment)
|
||||
appServer := newTestAppServerWithEnforcerConfigure(f, t, testApp, testDeployment)
|
||||
|
||||
noRoleCtx := context.Background()
|
||||
// nolint:staticcheck
|
||||
adminCtx := context.WithValue(noRoleCtx, "claims", &jwt.MapClaims{"groups": []string{"admin"}})
|
||||
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
_, err := appServer.Get(adminCtx, &application.ApplicationQuery{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.Get(noRoleCtx, &application.ApplicationQuery{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.Get(adminCtx, &application.ApplicationQuery{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("GetManifests", func(t *testing.T) {
|
||||
_, err := appServer.GetManifests(adminCtx, &application.ApplicationManifestQuery{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.GetManifests(noRoleCtx, &application.ApplicationManifestQuery{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.GetManifests(adminCtx, &application.ApplicationManifestQuery{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("ListResourceEvents", func(t *testing.T) {
|
||||
_, err := appServer.ListResourceEvents(adminCtx, &application.ApplicationResourceEventsQuery{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.ListResourceEvents(noRoleCtx, &application.ApplicationResourceEventsQuery{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.ListResourceEvents(adminCtx, &application.ApplicationResourceEventsQuery{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("UpdateSpec", func(t *testing.T) {
|
||||
_, err := appServer.UpdateSpec(adminCtx, &application.ApplicationUpdateSpecRequest{Name: pointer.String("test"), Spec: &appsv1.ApplicationSpec{
|
||||
Destination: appsv1.ApplicationDestination{Namespace: "default", Server: "https://cluster-api.com"},
|
||||
Source: appsv1.ApplicationSource{RepoURL: "https://some-fake-source", Path: "."},
|
||||
}})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.UpdateSpec(noRoleCtx, &application.ApplicationUpdateSpecRequest{Name: pointer.String("test"), Spec: &appsv1.ApplicationSpec{
|
||||
Destination: appsv1.ApplicationDestination{Namespace: "default", Server: "https://cluster-api.com"},
|
||||
Source: appsv1.ApplicationSource{RepoURL: "https://some-fake-source", Path: "."},
|
||||
}})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.UpdateSpec(adminCtx, &application.ApplicationUpdateSpecRequest{Name: pointer.String("doest-not-exist"), Spec: &appsv1.ApplicationSpec{
|
||||
Destination: appsv1.ApplicationDestination{Namespace: "default", Server: "https://cluster-api.com"},
|
||||
Source: appsv1.ApplicationSource{RepoURL: "https://some-fake-source", Path: "."},
|
||||
}})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("Patch", func(t *testing.T) {
|
||||
_, err := appServer.Patch(adminCtx, &application.ApplicationPatchRequest{Name: pointer.String("test"), Patch: pointer.String(`[{"op": "replace", "path": "/spec/source/path", "value": "foo"}]`)})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.Patch(noRoleCtx, &application.ApplicationPatchRequest{Name: pointer.String("test"), Patch: pointer.String(`[{"op": "replace", "path": "/spec/source/path", "value": "foo"}]`)})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.Patch(adminCtx, &application.ApplicationPatchRequest{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("GetResource", func(t *testing.T) {
|
||||
_, err := appServer.GetResource(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.GetResource(noRoleCtx, &application.ApplicationResourceRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.GetResource(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("doest-not-exist"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("PatchResource", func(t *testing.T) {
|
||||
_, err := appServer.PatchResource(adminCtx, &application.ApplicationResourcePatchRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test"), Patch: pointer.String(`[{"op": "replace", "path": "/spec/replicas", "value": 3}]`)})
|
||||
// This will always throw an error, because the kubectl mock for PatchResource is hard-coded to return nil.
|
||||
// The best we can do is to confirm we get past the permission check.
|
||||
assert.NotEqual(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.PatchResource(noRoleCtx, &application.ApplicationResourcePatchRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test"), Patch: pointer.String(`[{"op": "replace", "path": "/spec/replicas", "value": 3}]`)})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.PatchResource(adminCtx, &application.ApplicationResourcePatchRequest{Name: pointer.String("doest-not-exist"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test"), Patch: pointer.String(`[{"op": "replace", "path": "/spec/replicas", "value": 3}]`)})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("DeleteResource", func(t *testing.T) {
|
||||
_, err := appServer.DeleteResource(adminCtx, &application.ApplicationResourceDeleteRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.DeleteResource(noRoleCtx, &application.ApplicationResourceDeleteRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.DeleteResource(adminCtx, &application.ApplicationResourceDeleteRequest{Name: pointer.String("doest-not-exist"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("ResourceTree", func(t *testing.T) {
|
||||
_, err := appServer.ResourceTree(adminCtx, &application.ResourcesQuery{ApplicationName: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.ResourceTree(noRoleCtx, &application.ResourcesQuery{ApplicationName: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.ResourceTree(adminCtx, &application.ResourcesQuery{ApplicationName: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("RevisionMetadata", func(t *testing.T) {
|
||||
_, err := appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.RevisionMetadata(noRoleCtx, &application.RevisionMetadataQuery{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("ManagedResources", func(t *testing.T) {
|
||||
_, err := appServer.ManagedResources(adminCtx, &application.ResourcesQuery{ApplicationName: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.ManagedResources(noRoleCtx, &application.ResourcesQuery{ApplicationName: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.ManagedResources(adminCtx, &application.ResourcesQuery{ApplicationName: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("Sync", func(t *testing.T) {
|
||||
_, err := appServer.Sync(adminCtx, &application.ApplicationSyncRequest{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.Sync(noRoleCtx, &application.ApplicationSyncRequest{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.Sync(adminCtx, &application.ApplicationSyncRequest{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("TerminateOperation", func(t *testing.T) {
|
||||
// The sync operation is already started from the previous test. We just need to set the field that the
|
||||
// controller would set if this were an actual Argo CD environment.
|
||||
setSyncRunningOperationState(t, appServer)
|
||||
_, err := appServer.TerminateOperation(adminCtx, &application.OperationTerminateRequest{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.TerminateOperation(noRoleCtx, &application.OperationTerminateRequest{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.TerminateOperation(adminCtx, &application.OperationTerminateRequest{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("Rollback", func(t *testing.T) {
|
||||
unsetSyncRunningOperationState(t, appServer)
|
||||
_, err := appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.Rollback(noRoleCtx, &application.ApplicationRollbackRequest{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("ListResourceActions", func(t *testing.T) {
|
||||
_, err := appServer.ListResourceActions(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.ListResourceActions(noRoleCtx, &application.ApplicationResourceRequest{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.ListResourceActions(noRoleCtx, &application.ApplicationResourceRequest{Group: pointer.String("argoproj.io"), Kind: pointer.String("Application"), Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.ListResourceActions(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("RunResourceAction", func(t *testing.T) {
|
||||
_, err := appServer.RunResourceAction(adminCtx, &application.ResourceActionRunRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test"), Action: pointer.String("restart")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.RunResourceAction(noRoleCtx, &application.ResourceActionRunRequest{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.RunResourceAction(noRoleCtx, &application.ResourceActionRunRequest{Group: pointer.String("argoproj.io"), Kind: pointer.String("Application"), Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.RunResourceAction(adminCtx, &application.ResourceActionRunRequest{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("GetApplicationSyncWindows", func(t *testing.T) {
|
||||
_, err := appServer.GetApplicationSyncWindows(adminCtx, &application.ApplicationSyncWindowsQuery{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.GetApplicationSyncWindows(noRoleCtx, &application.ApplicationSyncWindowsQuery{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.GetApplicationSyncWindows(adminCtx, &application.ApplicationSyncWindowsQuery{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("WatchResourceTree", func(t *testing.T) {
|
||||
err := appServer.WatchResourceTree(&application.ResourcesQuery{ApplicationName: pointer.String("test")}, &TestResourceTreeServer{ctx: adminCtx})
|
||||
assert.NoError(t, err)
|
||||
err = appServer.WatchResourceTree(&application.ResourcesQuery{ApplicationName: pointer.String("test")}, &TestResourceTreeServer{ctx: noRoleCtx})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
err = appServer.WatchResourceTree(&application.ResourcesQuery{ApplicationName: pointer.String("does-not-exist")}, &TestResourceTreeServer{ctx: adminCtx})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
t.Run("PodLogs", func(t *testing.T) {
|
||||
err := appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: pointer.String("test")}, &TestPodLogsServer{ctx: adminCtx})
|
||||
assert.NoError(t, err)
|
||||
err = appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: pointer.String("test")}, &TestPodLogsServer{ctx: noRoleCtx})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
err = appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: pointer.String("does-not-exist")}, &TestPodLogsServer{ctx: adminCtx})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
|
||||
// Do this last so other stuff doesn't fail.
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
_, err := appServer.Delete(adminCtx, &application.ApplicationDeleteRequest{Name: pointer.String("test")})
|
||||
assert.NoError(t, err)
|
||||
_, err = appServer.Delete(noRoleCtx, &application.ApplicationDeleteRequest{Name: pointer.String("test")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
_, err = appServer.Delete(adminCtx, &application.ApplicationDeleteRequest{Name: pointer.String("doest-not-exist")})
|
||||
assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
|
||||
})
|
||||
}
|
||||
|
||||
// setSyncRunningOperationState simulates starting a sync operation on the given app.
|
||||
func setSyncRunningOperationState(t *testing.T, appServer *Server) {
|
||||
appIf := appServer.appclientset.ArgoprojV1alpha1().Applications("default")
|
||||
app, err := appIf.Get(context.Background(), "test", metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
// This sets the status that would be set by the controller usually.
|
||||
app.Status.OperationState = &appsv1.OperationState{Phase: synccommon.OperationRunning, Operation: appsv1.Operation{Sync: &appsv1.SyncOperation{}}}
|
||||
_, err = appIf.Update(context.Background(), app, metav1.UpdateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// unsetSyncRunningOperationState simulates finishing a sync operation on the given app.
|
||||
func unsetSyncRunningOperationState(t *testing.T, appServer *Server) {
|
||||
appIf := appServer.appclientset.ArgoprojV1alpha1().Applications("default")
|
||||
app, err := appIf.Get(context.Background(), "test", metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
app.Operation = nil
|
||||
app.Status.OperationState = nil
|
||||
_, err = appIf.Update(context.Background(), app, metav1.UpdateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func testListAppsWithLabels(t *testing.T, appQuery application.ApplicationQuery, appServer *Server) {
|
||||
validTests := []struct {
|
||||
testName string
|
||||
label string
|
||||
expectedResult []string
|
||||
}{
|
||||
{testName: "Equality based filtering using '=' operator",
|
||||
label: "key1=value1",
|
||||
expectedResult: []string{"App1"}},
|
||||
{testName: "Equality based filtering using '==' operator",
|
||||
label: "key1==value1",
|
||||
expectedResult: []string{"App1"}},
|
||||
{testName: "Equality based filtering using '!=' operator",
|
||||
label: "key1!=value1",
|
||||
expectedResult: []string{"App2", "App3"}},
|
||||
{testName: "Set based filtering using 'in' operator",
|
||||
label: "key1 in (value1, value3)",
|
||||
expectedResult: []string{"App1", "App3"}},
|
||||
{testName: "Set based filtering using 'notin' operator",
|
||||
label: "key1 notin (value1, value3)",
|
||||
expectedResult: []string{"App2"}},
|
||||
{testName: "Set based filtering using 'exists' operator",
|
||||
label: "key1",
|
||||
expectedResult: []string{"App1", "App2", "App3"}},
|
||||
{testName: "Set based filtering using 'not exists' operator",
|
||||
label: "!key2",
|
||||
expectedResult: []string{"App2", "App3"}},
|
||||
}
|
||||
//test valid scenarios
|
||||
for _, validTest := range validTests {
|
||||
t.Run(validTest.testName, func(t *testing.T) {
|
||||
appQuery.Selector = &validTest.label
|
||||
res, err := appServer.List(context.Background(), &appQuery)
|
||||
assert.NoError(t, err)
|
||||
apps := []string{}
|
||||
for i := range res.Items {
|
||||
apps = append(apps, res.Items[i].Name)
|
||||
}
|
||||
assert.Equal(t, validTest.expectedResult, apps)
|
||||
})
|
||||
}
|
||||
|
||||
invalidTests := []struct {
|
||||
testName string
|
||||
label string
|
||||
errorMesage string
|
||||
}{
|
||||
{testName: "Set based filtering using '>' operator",
|
||||
label: "key1>value1",
|
||||
errorMesage: "error parsing the selector"},
|
||||
{testName: "Set based filtering using '<' operator",
|
||||
label: "key1<value1",
|
||||
errorMesage: "error parsing the selector"},
|
||||
}
|
||||
//test invalid scenarios
|
||||
for _, invalidTest := range invalidTests {
|
||||
t.Run(invalidTest.testName, func(t *testing.T) {
|
||||
appQuery.Selector = &invalidTest.label
|
||||
_, err := appServer.List(context.Background(), &appQuery)
|
||||
assert.ErrorContains(t, err, invalidTest.errorMesage)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAppWithProjects(t *testing.T) {
|
||||
appServer := newTestAppServer(t, newTestApp(func(app *appsv1.Application) {
|
||||
app.Name = "App1"
|
||||
app.Spec.Project = "test-project1"
|
||||
}), newTestApp(func(app *appsv1.Application) {
|
||||
app.Name = "App2"
|
||||
app.Spec.Project = "test-project2"
|
||||
}), newTestApp(func(app *appsv1.Application) {
|
||||
app.Name = "App3"
|
||||
app.Spec.Project = "test-project3"
|
||||
}))
|
||||
|
||||
t.Run("List all apps", func(t *testing.T) {
|
||||
appQuery := application.ApplicationQuery{}
|
||||
appList, err := appServer.List(context.Background(), &appQuery)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, appList.Items, 3)
|
||||
})
|
||||
|
||||
t.Run("List apps with projects filter set", func(t *testing.T) {
|
||||
appQuery := application.ApplicationQuery{Projects: []string{"test-project1"}}
|
||||
appList, err := appServer.List(context.Background(), &appQuery)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, appList.Items, 1)
|
||||
for _, app := range appList.Items {
|
||||
assert.Equal(t, "test-project1", app.Spec.Project)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("List apps with project filter set (legacy field)", func(t *testing.T) {
|
||||
appQuery := application.ApplicationQuery{Project: []string{"test-project1"}}
|
||||
appList, err := appServer.List(context.Background(), &appQuery)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, appList.Items, 1)
|
||||
for _, app := range appList.Items {
|
||||
assert.Equal(t, "test-project1", app.Spec.Project)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("List apps with both projects and project filter set", func(t *testing.T) {
|
||||
// If the older field is present, we should use it instead of the newer field.
|
||||
appQuery := application.ApplicationQuery{Project: []string{"test-project1"}, Projects: []string{"test-project2"}}
|
||||
appList, err := appServer.List(context.Background(), &appQuery)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, appList.Items, 1)
|
||||
for _, app := range appList.Items {
|
||||
assert.Equal(t, "test-project1", app.Spec.Project)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestListApps(t *testing.T) {
|
||||
appServer := newTestAppServer(newTestApp(func(app *appsv1.Application) {
|
||||
appServer := newTestAppServer(t, newTestApp(func(app *appsv1.Application) {
|
||||
app.Name = "bcd"
|
||||
}), newTestApp(func(app *appsv1.Application) {
|
||||
app.Name = "abc"
|
||||
@@ -344,7 +876,7 @@ g, group-49, role:test3
|
||||
`
|
||||
_ = enf.SetUserPolicy(policy)
|
||||
}
|
||||
appServer := newTestAppServerWithEnforcerConfigure(f, objects...)
|
||||
appServer := newTestAppServerWithEnforcerConfigure(f, t, objects...)
|
||||
|
||||
res, err := appServer.List(ctx, &application.ApplicationQuery{})
|
||||
|
||||
@@ -358,7 +890,7 @@ g, group-49, role:test3
|
||||
|
||||
func TestCreateApp(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
appServer := newTestAppServer()
|
||||
appServer := newTestAppServer(t)
|
||||
testApp.Spec.Project = ""
|
||||
createReq := application.ApplicationCreateRequest{
|
||||
Application: testApp,
|
||||
@@ -371,7 +903,7 @@ func TestCreateApp(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateAppWithDestName(t *testing.T) {
|
||||
appServer := newTestAppServer()
|
||||
appServer := newTestAppServer(t)
|
||||
testApp := newTestAppWithDestName()
|
||||
createReq := application.ApplicationCreateRequest{
|
||||
Application: testApp,
|
||||
@@ -384,7 +916,7 @@ func TestCreateAppWithDestName(t *testing.T) {
|
||||
|
||||
func TestUpdateApp(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
testApp.Spec.Project = ""
|
||||
app, err := appServer.Update(context.Background(), &application.ApplicationUpdateRequest{
|
||||
Application: testApp,
|
||||
@@ -395,7 +927,7 @@ func TestUpdateApp(t *testing.T) {
|
||||
|
||||
func TestUpdateAppSpec(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
testApp.Spec.Project = ""
|
||||
spec, err := appServer.UpdateSpec(context.Background(), &application.ApplicationUpdateSpecRequest{
|
||||
Name: &testApp.Name,
|
||||
@@ -410,7 +942,7 @@ func TestUpdateAppSpec(t *testing.T) {
|
||||
|
||||
func TestDeleteApp(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
appServer := newTestAppServer()
|
||||
appServer := newTestAppServer(t)
|
||||
createReq := application.ApplicationCreateRequest{
|
||||
Application: newTestApp(),
|
||||
}
|
||||
@@ -495,20 +1027,9 @@ func TestDeleteApp(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteApp_InvalidName(t *testing.T) {
|
||||
appServer := newTestAppServer()
|
||||
_, err := appServer.Delete(context.Background(), &application.ApplicationDeleteRequest{
|
||||
Name: pointer.StringPtr("foo"),
|
||||
})
|
||||
if !assert.Error(t, err) {
|
||||
return
|
||||
}
|
||||
assert.True(t, apierrors.IsNotFound(err))
|
||||
}
|
||||
|
||||
func TestSyncAndTerminate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
appServer := newTestAppServer()
|
||||
appServer := newTestAppServer(t)
|
||||
testApp := newTestApp()
|
||||
testApp.Spec.Source.RepoURL = "https://github.com/argoproj/argo-cd.git"
|
||||
createReq := application.ApplicationCreateRequest{
|
||||
@@ -548,7 +1069,7 @@ func TestSyncAndTerminate(t *testing.T) {
|
||||
|
||||
func TestSyncHelm(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
appServer := newTestAppServer()
|
||||
appServer := newTestAppServer(t)
|
||||
testApp := newTestApp()
|
||||
testApp.Spec.Source.RepoURL = "https://argoproj.github.io/argo-helm"
|
||||
testApp.Spec.Source.Path = ""
|
||||
@@ -572,7 +1093,7 @@ func TestSyncHelm(t *testing.T) {
|
||||
|
||||
func TestSyncGit(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
appServer := newTestAppServer()
|
||||
appServer := newTestAppServer(t)
|
||||
testApp := newTestApp()
|
||||
testApp.Spec.Source.RepoURL = "https://github.com/org/test"
|
||||
testApp.Spec.Source.Path = "deploy"
|
||||
@@ -605,7 +1126,7 @@ func TestRollbackApp(t *testing.T) {
|
||||
Revision: "abc",
|
||||
Source: *testApp.Spec.Source.DeepCopy(),
|
||||
}}
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
|
||||
updatedApp, err := appServer.Rollback(context.Background(), &application.ApplicationRollbackRequest{
|
||||
Name: &testApp.Name,
|
||||
@@ -625,56 +1146,63 @@ func TestUpdateAppProject(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
// nolint:staticcheck
|
||||
ctx = context.WithValue(ctx, "claims", &jwt.StandardClaims{Subject: "admin"})
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
appServer.enf.SetDefaultRole("")
|
||||
|
||||
// Verify normal update works (without changing project)
|
||||
_ = appServer.enf.SetBuiltinPolicy(`p, admin, applications, update, default/test-app, allow`)
|
||||
_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
assert.NoError(t, err)
|
||||
t.Run("update without changing project", func(t *testing.T) {
|
||||
_ = appServer.enf.SetBuiltinPolicy(`p, admin, applications, update, default/test-app, allow`)
|
||||
_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
// Verify caller cannot update to another project
|
||||
testApp.Spec.Project = "my-proj"
|
||||
_, err = appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
assert.Equal(t, status.Code(err), codes.PermissionDenied)
|
||||
t.Run("cannot update to another project", func(t *testing.T) {
|
||||
testApp.Spec.Project = "my-proj"
|
||||
_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
assert.Equal(t, status.Code(err), codes.PermissionDenied)
|
||||
})
|
||||
|
||||
// Verify inability to change projects without create privileges in new project
|
||||
_ = appServer.enf.SetBuiltinPolicy(`
|
||||
t.Run("cannot change projects without create privileges", func(t *testing.T) {
|
||||
_ = appServer.enf.SetBuiltinPolicy(`
|
||||
p, admin, applications, update, default/test-app, allow
|
||||
p, admin, applications, update, my-proj/test-app, allow
|
||||
`)
|
||||
_, err = appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
statusErr := grpc.UnwrapGRPCStatus(err)
|
||||
assert.NotNil(t, statusErr)
|
||||
assert.Equal(t, codes.PermissionDenied, statusErr.Code())
|
||||
_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
statusErr := grpc.UnwrapGRPCStatus(err)
|
||||
assert.NotNil(t, statusErr)
|
||||
assert.Equal(t, codes.PermissionDenied, statusErr.Code())
|
||||
})
|
||||
|
||||
// Verify inability to change projects without update privileges in new project
|
||||
_ = appServer.enf.SetBuiltinPolicy(`
|
||||
t.Run("cannot change projects without update privileges in new project", func(t *testing.T) {
|
||||
_ = appServer.enf.SetBuiltinPolicy(`
|
||||
p, admin, applications, update, default/test-app, allow
|
||||
p, admin, applications, create, my-proj/test-app, allow
|
||||
`)
|
||||
_, err = appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
assert.Equal(t, status.Code(err), codes.PermissionDenied)
|
||||
_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
assert.Equal(t, status.Code(err), codes.PermissionDenied)
|
||||
})
|
||||
|
||||
// Verify inability to change projects without update privileges in old project
|
||||
_ = appServer.enf.SetBuiltinPolicy(`
|
||||
t.Run("cannot change projects without update privileges in old project", func(t *testing.T) {
|
||||
_ = appServer.enf.SetBuiltinPolicy(`
|
||||
p, admin, applications, create, my-proj/test-app, allow
|
||||
p, admin, applications, update, my-proj/test-app, allow
|
||||
`)
|
||||
_, err = appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
statusErr = grpc.UnwrapGRPCStatus(err)
|
||||
assert.NotNil(t, statusErr)
|
||||
assert.Equal(t, codes.PermissionDenied, statusErr.Code())
|
||||
_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
statusErr := grpc.UnwrapGRPCStatus(err)
|
||||
assert.NotNil(t, statusErr)
|
||||
assert.Equal(t, codes.PermissionDenied, statusErr.Code())
|
||||
})
|
||||
|
||||
// Verify can update project with proper permissions
|
||||
_ = appServer.enf.SetBuiltinPolicy(`
|
||||
t.Run("can update project with proper permissions", func(t *testing.T) {
|
||||
// Verify can update project with proper permissions
|
||||
_ = appServer.enf.SetBuiltinPolicy(`
|
||||
p, admin, applications, update, default/test-app, allow
|
||||
p, admin, applications, create, my-proj/test-app, allow
|
||||
p, admin, applications, update, my-proj/test-app, allow
|
||||
`)
|
||||
updatedApp, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "my-proj", updatedApp.Spec.Project)
|
||||
updatedApp, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "my-proj", updatedApp.Spec.Project)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAppJsonPatch(t *testing.T) {
|
||||
@@ -682,7 +1210,7 @@ func TestAppJsonPatch(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
// nolint:staticcheck
|
||||
ctx = context.WithValue(ctx, "claims", &jwt.StandardClaims{Subject: "admin"})
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
appServer.enf.SetDefaultRole("")
|
||||
|
||||
app, err := appServer.Patch(ctx, &application.ApplicationPatchRequest{Name: &testApp.Name, Patch: pointer.String("garbage")})
|
||||
@@ -707,7 +1235,7 @@ func TestAppMergePatch(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
// nolint:staticcheck
|
||||
ctx = context.WithValue(ctx, "claims", &jwt.StandardClaims{Subject: "admin"})
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
appServer.enf.SetDefaultRole("")
|
||||
|
||||
app, err := appServer.Patch(ctx, &application.ApplicationPatchRequest{
|
||||
@@ -720,7 +1248,7 @@ func TestServer_GetApplicationSyncWindowsState(t *testing.T) {
|
||||
t.Run("Active", func(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
testApp.Spec.Project = "proj-maint"
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
|
||||
active, err := appServer.GetApplicationSyncWindows(context.Background(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name})
|
||||
assert.NoError(t, err)
|
||||
@@ -729,7 +1257,7 @@ func TestServer_GetApplicationSyncWindowsState(t *testing.T) {
|
||||
t.Run("Inactive", func(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
testApp.Spec.Project = "default"
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
|
||||
active, err := appServer.GetApplicationSyncWindows(context.Background(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name})
|
||||
assert.NoError(t, err)
|
||||
@@ -738,7 +1266,7 @@ func TestServer_GetApplicationSyncWindowsState(t *testing.T) {
|
||||
t.Run("ProjectDoesNotExist", func(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
testApp.Spec.Project = "none"
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
|
||||
active, err := appServer.GetApplicationSyncWindows(context.Background(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name})
|
||||
assert.Contains(t, err.Error(), "not found")
|
||||
@@ -749,8 +1277,14 @@ func TestServer_GetApplicationSyncWindowsState(t *testing.T) {
|
||||
func TestGetCachedAppState(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
testApp.ObjectMeta.ResourceVersion = "1"
|
||||
testApp.Spec.Project = "none"
|
||||
appServer := newTestAppServer(testApp)
|
||||
testApp.Spec.Project = "test-proj"
|
||||
testProj := &appsv1.AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-proj",
|
||||
Namespace: testNamespace,
|
||||
},
|
||||
}
|
||||
appServer := newTestAppServer(t, testApp, testProj)
|
||||
fakeClientSet := appServer.appclientset.(*apps.Clientset)
|
||||
t.Run("NoError", func(t *testing.T) {
|
||||
err := appServer.getCachedAppState(context.Background(), testApp, func() error {
|
||||
@@ -922,7 +1456,7 @@ func TestGetAppRefresh_NormalRefresh(t *testing.T) {
|
||||
defer cancel()
|
||||
testApp := newTestApp()
|
||||
testApp.ObjectMeta.ResourceVersion = "1"
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
|
||||
var patched int32
|
||||
|
||||
@@ -950,7 +1484,7 @@ func TestGetAppRefresh_HardRefresh(t *testing.T) {
|
||||
defer cancel()
|
||||
testApp := newTestApp()
|
||||
testApp.ObjectMeta.ResourceVersion = "1"
|
||||
appServer := newTestAppServer(testApp)
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
|
||||
var getAppDetailsQuery *apiclient.RepoServerAppDetailsQuery
|
||||
mockRepoServiceClient := mocks.RepoServerServiceClient{}
|
||||
|
||||
@@ -23,6 +23,14 @@ func (s *subscriber) matches(event *appv1.ApplicationWatchEvent) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Broadcaster is an interface for broadcasting application informer watch events to multiple subscribers.
|
||||
type Broadcaster interface {
|
||||
Subscribe(ch chan *appv1.ApplicationWatchEvent, filters ...func(event *appv1.ApplicationWatchEvent) bool) func()
|
||||
OnAdd(interface{})
|
||||
OnUpdate(interface{}, interface{})
|
||||
OnDelete(interface{})
|
||||
}
|
||||
|
||||
type broadcasterHandler struct {
|
||||
lock sync.Mutex
|
||||
subscribers []*subscriber
|
||||
|
||||
66
server/application/mocks/Broadcaster.go
Normal file
66
server/application/mocks/Broadcaster.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Code generated by mockery v2.13.1. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// Broadcaster is an autogenerated mock type for the Broadcaster type
|
||||
type Broadcaster struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// OnAdd provides a mock function with given fields: _a0
|
||||
func (_m *Broadcaster) OnAdd(_a0 interface{}) {
|
||||
_m.Called(_a0)
|
||||
}
|
||||
|
||||
// OnDelete provides a mock function with given fields: _a0
|
||||
func (_m *Broadcaster) OnDelete(_a0 interface{}) {
|
||||
_m.Called(_a0)
|
||||
}
|
||||
|
||||
// OnUpdate provides a mock function with given fields: _a0, _a1
|
||||
func (_m *Broadcaster) OnUpdate(_a0 interface{}, _a1 interface{}) {
|
||||
_m.Called(_a0, _a1)
|
||||
}
|
||||
|
||||
// Subscribe provides a mock function with given fields: ch, filters
|
||||
func (_m *Broadcaster) Subscribe(ch chan *v1alpha1.ApplicationWatchEvent, filters ...func(*v1alpha1.ApplicationWatchEvent) bool) func() {
|
||||
_va := make([]interface{}, len(filters))
|
||||
for _i := range filters {
|
||||
_va[_i] = filters[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ch)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 func()
|
||||
if rf, ok := ret.Get(0).(func(chan *v1alpha1.ApplicationWatchEvent, ...func(*v1alpha1.ApplicationWatchEvent) bool) func()); ok {
|
||||
r0 = rf(ch, filters...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(func())
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
type mockConstructorTestingTNewBroadcaster interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
// NewBroadcaster creates a new instance of Broadcaster. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
func NewBroadcaster(t mockConstructorTestingTNewBroadcaster) *Broadcaster {
|
||||
mock := &Broadcaster{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/util/db"
|
||||
"github.com/argoproj/argo-cd/v2/util/rbac"
|
||||
sessionmgr "github.com/argoproj/argo-cd/v2/util/session"
|
||||
"github.com/argoproj/argo-cd/v2/util/settings"
|
||||
)
|
||||
|
||||
type terminalHandler struct {
|
||||
@@ -91,6 +92,26 @@ func isValidContainerName(name string) bool {
|
||||
return len(validationErrors) == 0
|
||||
}
|
||||
|
||||
type GetSettingsFunc func() (*settings.ArgoCDSettings, error)
|
||||
|
||||
// WithFeatureFlagMiddleware is an HTTP middleware to verify if the terminal
|
||||
// feature is enabled before invoking the main handler
|
||||
func (s *terminalHandler) WithFeatureFlagMiddleware(getSettings GetSettingsFunc) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
argocdSettings, err := getSettings()
|
||||
if err != nil {
|
||||
log.Errorf("error executing WithFeatureFlagMiddleware: error getting settings: %s", err)
|
||||
http.Error(w, "Failed to get settings", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if !argocdSettings.ExecEnabled {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
s.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *terminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster"
|
||||
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
servercache "github.com/argoproj/argo-cd/v2/server/cache"
|
||||
@@ -134,7 +135,7 @@ func (s *Server) Get(ctx context.Context, q *cluster.ClusterQuery) (*appv1.Clust
|
||||
func (s *Server) getClusterWith403IfNotExist(ctx context.Context, q *cluster.ClusterQuery) (*appv1.Cluster, error) {
|
||||
repo, err := s.getCluster(ctx, q)
|
||||
if err != nil || repo == nil {
|
||||
return nil, status.Error(codes.PermissionDenied, "permission denied")
|
||||
return nil, common.PermissionDeniedAPIError
|
||||
}
|
||||
return repo, nil
|
||||
}
|
||||
@@ -220,14 +221,14 @@ func (s *Server) Update(ctx context.Context, q *cluster.ClusterUpdateRequest) (*
|
||||
}
|
||||
|
||||
// verify that user can do update inside project where cluster is located
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, createRBACObject(c.Project, q.Cluster.Server)); err != nil {
|
||||
return nil, err
|
||||
if !s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, createRBACObject(c.Project, c.Server)) {
|
||||
return nil, common.PermissionDeniedAPIError
|
||||
}
|
||||
|
||||
if len(q.UpdatedFields) == 0 || sets.NewString(q.UpdatedFields...).Has("project") {
|
||||
// verify that user can do update inside project where cluster will be located
|
||||
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, createRBACObject(q.Cluster.Project, q.Cluster.Server)); err != nil {
|
||||
return nil, err
|
||||
if !s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, createRBACObject(q.Cluster.Project, c.Server)) {
|
||||
return nil, common.PermissionDeniedAPIError
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package cluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -48,6 +49,117 @@ func newNoopEnforcer() *rbac.Enforcer {
|
||||
return enf
|
||||
}
|
||||
|
||||
func TestUpdateCluster_RejectInvalidParams(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
request clusterapi.ClusterUpdateRequest
|
||||
}{
|
||||
{
|
||||
name: "allowed cluster URL in body, disallowed cluster URL in query",
|
||||
request: clusterapi.ClusterUpdateRequest{Cluster: &v1alpha1.Cluster{Name: "", Server: "https://127.0.0.1", Project: "", ClusterResources: true}, Id: &clusterapi.ClusterID{Type: "", Value: "https://127.0.0.2"}, UpdatedFields: []string{"clusterResources", "project"}},
|
||||
},
|
||||
{
|
||||
name: "allowed cluster URL in body, disallowed cluster name in query",
|
||||
request: clusterapi.ClusterUpdateRequest{Cluster: &v1alpha1.Cluster{Name: "", Server: "https://127.0.0.1", Project: "", ClusterResources: true}, Id: &clusterapi.ClusterID{Type: "name", Value: "disallowed-unscoped"}, UpdatedFields: []string{"clusterResources", "project"}},
|
||||
},
|
||||
{
|
||||
name: "allowed cluster URL in body, disallowed cluster name in query, changing unscoped to scoped",
|
||||
request: clusterapi.ClusterUpdateRequest{Cluster: &v1alpha1.Cluster{Name: "", Server: "https://127.0.0.1", Project: "allowed-project", ClusterResources: true}, Id: &clusterapi.ClusterID{Type: "", Value: "https://127.0.0.2"}, UpdatedFields: []string{"clusterResources", "project"}},
|
||||
},
|
||||
{
|
||||
name: "allowed cluster URL in body, disallowed cluster URL in query, changing unscoped to scoped",
|
||||
request: clusterapi.ClusterUpdateRequest{Cluster: &v1alpha1.Cluster{Name: "", Server: "https://127.0.0.1", Project: "allowed-project", ClusterResources: true}, Id: &clusterapi.ClusterID{Type: "name", Value: "disallowed-unscoped"}, UpdatedFields: []string{"clusterResources", "project"}},
|
||||
},
|
||||
}
|
||||
|
||||
db := &dbmocks.ArgoDB{}
|
||||
|
||||
clusters := []v1alpha1.Cluster{
|
||||
{
|
||||
Name: "allowed-unscoped",
|
||||
Server: "https://127.0.0.1",
|
||||
},
|
||||
{
|
||||
Name: "disallowed-unscoped",
|
||||
Server: "https://127.0.0.2",
|
||||
},
|
||||
{
|
||||
Name: "allowed-scoped",
|
||||
Server: "https://127.0.0.3",
|
||||
Project: "allowed-project",
|
||||
},
|
||||
{
|
||||
Name: "disallowed-scoped",
|
||||
Server: "https://127.0.0.4",
|
||||
Project: "disallowed-project",
|
||||
},
|
||||
}
|
||||
|
||||
db.On("ListClusters", mock.Anything).Return(
|
||||
func(ctx context.Context) *v1alpha1.ClusterList {
|
||||
return &v1alpha1.ClusterList{
|
||||
ListMeta: v1.ListMeta{},
|
||||
Items: clusters,
|
||||
}
|
||||
},
|
||||
func(ctx context.Context) error {
|
||||
return nil
|
||||
},
|
||||
)
|
||||
db.On("UpdateCluster", mock.Anything, mock.Anything).Return(
|
||||
func(ctx context.Context, c *v1alpha1.Cluster) *v1alpha1.Cluster {
|
||||
for _, cluster := range clusters {
|
||||
if c.Server == cluster.Server {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(ctx context.Context, c *v1alpha1.Cluster) error {
|
||||
for _, cluster := range clusters {
|
||||
if c.Server == cluster.Server {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("cluster '%s' not found", c.Server)
|
||||
},
|
||||
)
|
||||
db.On("GetCluster", mock.Anything, mock.Anything).Return(
|
||||
func(ctx context.Context, server string) *v1alpha1.Cluster {
|
||||
for _, cluster := range clusters {
|
||||
if server == cluster.Server {
|
||||
return &cluster
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(ctx context.Context, server string) error {
|
||||
for _, cluster := range clusters {
|
||||
if server == cluster.Server {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("cluster '%s' not found", server)
|
||||
},
|
||||
)
|
||||
|
||||
enf := rbac.NewEnforcer(fake.NewSimpleClientset(test.NewFakeConfigMap()), test.FakeArgoCDNamespace, common.ArgoCDConfigMapName, nil)
|
||||
_ = enf.SetBuiltinPolicy(`p, role:test, clusters, *, https://127.0.0.1, allow
|
||||
p, role:test, clusters, *, allowed-project/*, allow`)
|
||||
enf.SetDefaultRole("role:test")
|
||||
server := NewServer(db, enf, newServerInMemoryCache(), &kubetest.MockKubectlCmd{})
|
||||
|
||||
for _, c := range testCases {
|
||||
cc := c
|
||||
t.Run(cc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
out, err := server.Update(context.Background(), &cc.request)
|
||||
require.Nil(t, out)
|
||||
assert.ErrorIs(t, err, common.PermissionDeniedAPIError)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCluster_UrlEncodedName(t *testing.T) {
|
||||
db := &dbmocks.ArgoDB{}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
netCtx "context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
goio "io"
|
||||
@@ -24,8 +25,6 @@ import (
|
||||
// nolint:staticcheck
|
||||
golang_proto "github.com/golang/protobuf/proto"
|
||||
|
||||
netCtx "context"
|
||||
|
||||
"github.com/argoproj/pkg/sync"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
@@ -55,6 +54,8 @@ import (
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apiclient"
|
||||
accountpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/account"
|
||||
@@ -112,7 +113,6 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/util/swagger"
|
||||
tlsutil "github.com/argoproj/argo-cd/v2/util/tls"
|
||||
"github.com/argoproj/argo-cd/v2/util/webhook"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const maxConcurrentLoginRequestsCountEnv = "ARGOCD_MAX_CONCURRENT_LOGIN_REQUESTS_COUNT"
|
||||
@@ -423,8 +423,9 @@ func (a *ArgoCDServer) Run(ctx context.Context, listeners *Listeners) {
|
||||
|
||||
// If not matched, we assume that its TLS.
|
||||
tlsl := tcpm.Match(cmux.Any())
|
||||
tlsConfig := tls.Config{
|
||||
Certificates: []tls.Certificate{*a.settings.Certificate},
|
||||
tlsConfig := tls.Config{}
|
||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return a.settings.Certificate, nil
|
||||
}
|
||||
if a.TLSConfigCustomizer != nil {
|
||||
a.TLSConfigCustomizer(&tlsConfig)
|
||||
@@ -566,8 +567,8 @@ func (a *ArgoCDServer) watchSettings() {
|
||||
newCert, newCertKey = tlsutil.EncodeX509KeyPairString(*a.settings.Certificate)
|
||||
}
|
||||
if newCert != prevCert || newCertKey != prevCertKey {
|
||||
log.Infof("tls certificate modified. restarting")
|
||||
break
|
||||
log.Infof("tls certificate modified. reloading certificate")
|
||||
// No need to break out of this loop since TlsConfig.GetCertificate will automagically reload the cert.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -680,6 +681,7 @@ func (a *ArgoCDServer) newGRPCServer() (*grpc.Server, application.AppResourceTre
|
||||
a.AppClientset,
|
||||
a.appLister,
|
||||
a.appInformer,
|
||||
nil,
|
||||
a.RepoClientset,
|
||||
a.Cache,
|
||||
kubectl,
|
||||
@@ -812,40 +814,10 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
|
||||
}
|
||||
mux.Handle("/api/", handler)
|
||||
|
||||
terminalHandler := application.NewHandler(a.appLister, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells)
|
||||
mux.HandleFunc("/terminal", func(writer http.ResponseWriter, request *http.Request) {
|
||||
argocdSettings, err := a.settingsMgr.GetSettings()
|
||||
if err != nil {
|
||||
http.Error(writer, fmt.Sprintf("Failed to get settings: %v", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if !argocdSettings.ExecEnabled {
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if !a.DisableAuth {
|
||||
ctx := request.Context()
|
||||
cookies := request.Cookies()
|
||||
tokenString, err := httputil.JoinCookies(common.AuthCookieName, cookies)
|
||||
if err == nil && jwtutil.IsValid(tokenString) {
|
||||
claims, _, err := a.sessionMgr.VerifyToken(tokenString)
|
||||
if err != nil {
|
||||
// nolint:staticcheck
|
||||
ctx = context.WithValue(ctx, util_session.AuthErrorCtxKey, err)
|
||||
} else if claims != nil {
|
||||
// Add claims to the context to inspect for RBAC
|
||||
// nolint:staticcheck
|
||||
ctx = context.WithValue(ctx, "claims", claims)
|
||||
}
|
||||
request = request.WithContext(ctx)
|
||||
} else {
|
||||
writer.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
terminalHandler.ServeHTTP(writer, request)
|
||||
})
|
||||
terminal := application.NewHandler(a.appLister, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells).
|
||||
WithFeatureFlagMiddleware(a.settingsMgr.GetSettings)
|
||||
th := util_session.WithAuthMiddleware(a.DisableAuth, a.sessionMgr, terminal)
|
||||
mux.Handle("/terminal", th)
|
||||
|
||||
mustRegisterGWHandler(versionpkg.RegisterVersionServiceHandler, ctx, gwmux, conn)
|
||||
mustRegisterGWHandler(clusterpkg.RegisterClusterServiceHandler, ctx, gwmux, conn)
|
||||
|
||||
@@ -33,6 +33,7 @@ import (
|
||||
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
|
||||
"github.com/argoproj/argo-cd/v2/util/rbac"
|
||||
settings_util "github.com/argoproj/argo-cd/v2/util/settings"
|
||||
testutil "github.com/argoproj/argo-cd/v2/util/test"
|
||||
)
|
||||
|
||||
func fakeServer() (*ArgoCDServer, func()) {
|
||||
@@ -508,7 +509,7 @@ func dexMockHandler(t *testing.T, url string) func(http.ResponseWriter, *http.Re
|
||||
}
|
||||
}
|
||||
|
||||
func getTestServer(t *testing.T, anonymousEnabled bool, withFakeSSO bool) (argocd *ArgoCDServer, dexURL string) {
|
||||
func getTestServer(t *testing.T, anonymousEnabled bool, withFakeSSO bool, useDexForSSO bool) (argocd *ArgoCDServer, oidcURL string) {
|
||||
cm := test.NewFakeConfigMap()
|
||||
if anonymousEnabled {
|
||||
cm.Data["users.anonymous.enabled"] = "true"
|
||||
@@ -519,9 +520,14 @@ func getTestServer(t *testing.T, anonymousEnabled bool, withFakeSSO bool) (argoc
|
||||
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
dexMockHandler(t, ts.URL)(w, r)
|
||||
})
|
||||
oidcServer := ts
|
||||
if !useDexForSSO {
|
||||
oidcServer = testutil.GetOIDCTestServer(t)
|
||||
}
|
||||
if withFakeSSO {
|
||||
cm.Data["url"] = ts.URL
|
||||
cm.Data["dex.config"] = `
|
||||
if useDexForSSO {
|
||||
cm.Data["dex.config"] = `
|
||||
connectors:
|
||||
# OIDC
|
||||
- type: OIDC
|
||||
@@ -531,6 +537,19 @@ connectors:
|
||||
issuer: https://auth.example.gom
|
||||
clientID: test-client
|
||||
clientSecret: $dex.oidc.clientSecret`
|
||||
} else {
|
||||
oidcConfig := settings_util.OIDCConfig{
|
||||
Name: "Okta",
|
||||
Issuer: oidcServer.URL,
|
||||
ClientID: "argo-cd",
|
||||
ClientSecret: "$oidc.okta.clientSecret",
|
||||
}
|
||||
oidcConfigString, err := yaml.Marshal(oidcConfig)
|
||||
require.NoError(t, err)
|
||||
cm.Data["oidc.config"] = string(oidcConfigString)
|
||||
// Avoid bothering with certs for local tests.
|
||||
cm.Data["oidc.tls.insecure.skip.verify"] = "true"
|
||||
}
|
||||
}
|
||||
secret := test.NewFakeSecret()
|
||||
kubeclientset := fake.NewSimpleClientset(cm, secret)
|
||||
@@ -540,27 +559,32 @@ connectors:
|
||||
KubeClientset: kubeclientset,
|
||||
AppClientset: appClientSet,
|
||||
}
|
||||
if withFakeSSO {
|
||||
if withFakeSSO && useDexForSSO {
|
||||
argoCDOpts.DexServerAddr = ts.URL
|
||||
}
|
||||
argocd = NewServer(context.Background(), argoCDOpts)
|
||||
return argocd, ts.URL
|
||||
return argocd, oidcServer.URL
|
||||
}
|
||||
|
||||
func TestAuthenticate_3rd_party_JWTs(t *testing.T) {
|
||||
// Marshaling single strings to strings is typical, so we test for this relatively common behavior.
|
||||
jwt.MarshalSingleStringAsArray = false
|
||||
|
||||
type testData struct {
|
||||
test string
|
||||
anonymousEnabled bool
|
||||
claims jwt.RegisteredClaims
|
||||
expectedErrorContains string
|
||||
expectedClaims interface{}
|
||||
useDex bool
|
||||
}
|
||||
var tests = []testData{
|
||||
// Dex
|
||||
{
|
||||
test: "anonymous disabled, no audience",
|
||||
anonymousEnabled: false,
|
||||
claims: jwt.RegisteredClaims{},
|
||||
expectedErrorContains: "no audience found in the token",
|
||||
claims: jwt.RegisteredClaims{ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))},
|
||||
expectedErrorContains: common.TokenVerificationError,
|
||||
expectedClaims: nil,
|
||||
},
|
||||
{
|
||||
@@ -573,31 +597,95 @@ func TestAuthenticate_3rd_party_JWTs(t *testing.T) {
|
||||
{
|
||||
test: "anonymous disabled, unexpired token, admin claim",
|
||||
anonymousEnabled: false,
|
||||
claims: jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))},
|
||||
expectedErrorContains: "id token signed with unsupported algorithm",
|
||||
claims: jwt.RegisteredClaims{Audience: jwt.ClaimStrings{common.ArgoCDClientAppID}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))},
|
||||
expectedErrorContains: common.TokenVerificationError,
|
||||
expectedClaims: nil,
|
||||
},
|
||||
{
|
||||
test: "anonymous enabled, unexpired token, admin claim",
|
||||
anonymousEnabled: true,
|
||||
claims: jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))},
|
||||
claims: jwt.RegisteredClaims{Audience: jwt.ClaimStrings{common.ArgoCDClientAppID}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))},
|
||||
expectedErrorContains: "",
|
||||
expectedClaims: "",
|
||||
},
|
||||
{
|
||||
test: "anonymous disabled, expired token, admin claim",
|
||||
anonymousEnabled: false,
|
||||
claims: jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now())},
|
||||
expectedErrorContains: "token is expired",
|
||||
claims: jwt.RegisteredClaims{Audience: jwt.ClaimStrings{common.ArgoCDClientAppID}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now())},
|
||||
expectedErrorContains: common.TokenVerificationError,
|
||||
expectedClaims: jwt.RegisteredClaims{Issuer: "sso"},
|
||||
},
|
||||
{
|
||||
test: "anonymous enabled, expired token, admin claim",
|
||||
anonymousEnabled: true,
|
||||
claims: jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now())},
|
||||
claims: jwt.RegisteredClaims{Audience: jwt.ClaimStrings{common.ArgoCDClientAppID}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now())},
|
||||
expectedErrorContains: "",
|
||||
expectedClaims: "",
|
||||
},
|
||||
{
|
||||
test: "anonymous disabled, unexpired token, admin claim, incorrect audience",
|
||||
anonymousEnabled: false,
|
||||
claims: jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"incorrect-audience"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))},
|
||||
expectedErrorContains: common.TokenVerificationError,
|
||||
expectedClaims: nil,
|
||||
},
|
||||
// External OIDC (not bundled Dex)
|
||||
{
|
||||
test: "external OIDC: anonymous disabled, no audience",
|
||||
anonymousEnabled: false,
|
||||
claims: jwt.RegisteredClaims{ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))},
|
||||
useDex: true,
|
||||
expectedErrorContains: common.TokenVerificationError,
|
||||
expectedClaims: nil,
|
||||
},
|
||||
{
|
||||
test: "external OIDC: anonymous enabled, no audience",
|
||||
anonymousEnabled: true,
|
||||
claims: jwt.RegisteredClaims{},
|
||||
useDex: true,
|
||||
expectedErrorContains: "",
|
||||
expectedClaims: "",
|
||||
},
|
||||
{
|
||||
test: "external OIDC: anonymous disabled, unexpired token, admin claim",
|
||||
anonymousEnabled: false,
|
||||
claims: jwt.RegisteredClaims{Audience: jwt.ClaimStrings{common.ArgoCDClientAppID}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))},
|
||||
useDex: true,
|
||||
expectedErrorContains: common.TokenVerificationError,
|
||||
expectedClaims: nil,
|
||||
},
|
||||
{
|
||||
test: "external OIDC: anonymous enabled, unexpired token, admin claim",
|
||||
anonymousEnabled: true,
|
||||
claims: jwt.RegisteredClaims{Audience: jwt.ClaimStrings{common.ArgoCDClientAppID}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))},
|
||||
useDex: true,
|
||||
expectedErrorContains: "",
|
||||
expectedClaims: "",
|
||||
},
|
||||
{
|
||||
test: "external OIDC: anonymous disabled, expired token, admin claim",
|
||||
anonymousEnabled: false,
|
||||
claims: jwt.RegisteredClaims{Audience: jwt.ClaimStrings{common.ArgoCDClientAppID}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now())},
|
||||
useDex: true,
|
||||
expectedErrorContains: common.TokenVerificationError,
|
||||
expectedClaims: jwt.RegisteredClaims{Issuer: "sso"},
|
||||
},
|
||||
{
|
||||
test: "external OIDC: anonymous enabled, expired token, admin claim",
|
||||
anonymousEnabled: true,
|
||||
claims: jwt.RegisteredClaims{Audience: jwt.ClaimStrings{common.ArgoCDClientAppID}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now())},
|
||||
useDex: true,
|
||||
expectedErrorContains: "",
|
||||
expectedClaims: "",
|
||||
},
|
||||
{
|
||||
test: "external OIDC: anonymous disabled, unexpired token, admin claim, incorrect audience",
|
||||
anonymousEnabled: false,
|
||||
claims: jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"incorrect-audience"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))},
|
||||
useDex: true,
|
||||
expectedErrorContains: common.TokenVerificationError,
|
||||
expectedClaims: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testData := range tests {
|
||||
@@ -609,8 +697,13 @@ func TestAuthenticate_3rd_party_JWTs(t *testing.T) {
|
||||
// Must be declared here to avoid race.
|
||||
ctx := context.Background() //nolint:ineffassign,staticcheck
|
||||
|
||||
argocd, dexURL := getTestServer(t, testDataCopy.anonymousEnabled, true)
|
||||
testDataCopy.claims.Issuer = fmt.Sprintf("%s/api/dex", dexURL)
|
||||
argocd, oidcURL := getTestServer(t, testDataCopy.anonymousEnabled, true, testDataCopy.useDex)
|
||||
|
||||
if testDataCopy.useDex {
|
||||
testDataCopy.claims.Issuer = fmt.Sprintf("%s/api/dex", oidcURL)
|
||||
} else {
|
||||
testDataCopy.claims.Issuer = oidcURL
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, testDataCopy.claims)
|
||||
tokenString, err := token.SignedString([]byte("key"))
|
||||
require.NoError(t, err)
|
||||
@@ -660,7 +753,7 @@ func TestAuthenticate_no_request_metadata(t *testing.T) {
|
||||
t.Run(testDataCopy.test, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
argocd, _ := getTestServer(t, testDataCopy.anonymousEnabled, true)
|
||||
argocd, _ := getTestServer(t, testDataCopy.anonymousEnabled, true, true)
|
||||
ctx := context.Background()
|
||||
|
||||
ctx, err := argocd.Authenticate(ctx)
|
||||
@@ -706,7 +799,7 @@ func TestAuthenticate_no_SSO(t *testing.T) {
|
||||
// Must be declared here to avoid race.
|
||||
ctx := context.Background() //nolint:ineffassign,staticcheck
|
||||
|
||||
argocd, dexURL := getTestServer(t, testDataCopy.anonymousEnabled, false)
|
||||
argocd, dexURL := getTestServer(t, testDataCopy.anonymousEnabled, false, true)
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{Issuer: fmt.Sprintf("%s/api/dex", dexURL)})
|
||||
tokenString, err := token.SignedString([]byte("key"))
|
||||
require.NoError(t, err)
|
||||
@@ -779,7 +872,7 @@ func TestAuthenticate_bad_request_metadata(t *testing.T) {
|
||||
test: "anonymous disabled, bad auth header",
|
||||
anonymousEnabled: false,
|
||||
metadata: metadata.MD{"authorization": []string{"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiJ9.TGGTTHuuGpEU8WgobXxkrBtW3NiR3dgw5LR-1DEW3BQ"}},
|
||||
expectedErrorMessage: "no audience found in the token",
|
||||
expectedErrorMessage: common.TokenVerificationError,
|
||||
expectedClaims: nil,
|
||||
},
|
||||
{
|
||||
@@ -793,7 +886,7 @@ func TestAuthenticate_bad_request_metadata(t *testing.T) {
|
||||
test: "anonymous disabled, bad auth cookie",
|
||||
anonymousEnabled: false,
|
||||
metadata: metadata.MD{"grpcgateway-cookie": []string{"argocd.token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiJ9.TGGTTHuuGpEU8WgobXxkrBtW3NiR3dgw5LR-1DEW3BQ"}},
|
||||
expectedErrorMessage: "no audience found in the token",
|
||||
expectedErrorMessage: common.TokenVerificationError,
|
||||
expectedClaims: nil,
|
||||
},
|
||||
{
|
||||
@@ -814,7 +907,7 @@ func TestAuthenticate_bad_request_metadata(t *testing.T) {
|
||||
// Must be declared here to avoid race.
|
||||
ctx := context.Background() //nolint:ineffassign,staticcheck
|
||||
|
||||
argocd, _ := getTestServer(t, testDataCopy.anonymousEnabled, true)
|
||||
argocd, _ := getTestServer(t, testDataCopy.anonymousEnabled, true, true)
|
||||
ctx = metadata.NewIncomingContext(context.Background(), testDataCopy.metadata)
|
||||
|
||||
ctx, err := argocd.Authenticate(ctx)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM redis:7.0.4 as redis
|
||||
FROM docker.io/library/redis:7.0.8-alpine as redis
|
||||
|
||||
FROM node:12.18.4-buster as node
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
controller: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
|
||||
api-server: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_BINARY_NAME=argocd-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} "
|
||||
dex: sh -c "test $ARGOCD_IN_CI = true && exit 0; ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/cmd gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:v2.30.0 serve /dex.yaml"
|
||||
controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
|
||||
api-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_BINARY_NAME=argocd-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} "
|
||||
dex: sh -c "test $ARGOCD_IN_CI = true && exit 0; ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/cmd gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:v2.36.0 serve /dex.yaml"
|
||||
redis: sh -c "/usr/local/bin/redis-server --save "" --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}"
|
||||
repo-server: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_BINARY_NAME=argocd-repo-server $COMMAND --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"
|
||||
ui: sh -c "test $ARGOCD_IN_CI = true && exit 0; cd ui && ARGOCD_E2E_YARN_HOST=0.0.0.0 ${ARGOCD_E2E_YARN_CMD:-yarn} start"
|
||||
|
||||
@@ -416,7 +416,9 @@ func TestInvalidAppProject(t *testing.T) {
|
||||
IgnoreErrors().
|
||||
CreateApp().
|
||||
Then().
|
||||
Expect(Error("", "application references project does-not-exist which does not exist"))
|
||||
// We're not allowed to infer whether the project exists based on this error message. Instead, we get a generic
|
||||
// permission denied error.
|
||||
Expect(Error("", "permission denied"))
|
||||
}
|
||||
|
||||
func TestAppDeletion(t *testing.T) {
|
||||
|
||||
@@ -782,6 +782,8 @@ func RestartRepoServer() {
|
||||
}
|
||||
FailOnErr(Run("", "kubectl", "rollout", "restart", "deployment", workload))
|
||||
FailOnErr(Run("", "kubectl", "rollout", "status", "deployment", workload))
|
||||
// wait longer to avoid error on s390x
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/superagent": "^4.1.15",
|
||||
"ansi-to-react": "^6.1.6",
|
||||
"argo-ui": "git+https://github.com/argoproj/argo-ui.git",
|
||||
"buffer": "^6.0.3",
|
||||
@@ -42,8 +43,7 @@
|
||||
"react-svg-piechart": "^2.4.2",
|
||||
"redoc": "^2.0.0-rc.64",
|
||||
"rxjs": "^6.6.6",
|
||||
"superagent": "^3.8.2",
|
||||
"superagent-promise": "^1.1.0",
|
||||
"superagent": "^7.1.3",
|
||||
"timezones-list": "3.0.1",
|
||||
"unidiff": "^1.0.2",
|
||||
"url": "^0.11.0",
|
||||
@@ -79,7 +79,6 @@
|
||||
"@types/react-router": "^4.0.27",
|
||||
"@types/react-router-dom": "^4.2.3",
|
||||
"@types/react-test-renderer": "^16.8.3",
|
||||
"@types/superagent": "^3.5.7",
|
||||
"add": "^2.0.6",
|
||||
"babel-jest": "^24.9.0",
|
||||
"babel-loader": "^8.0.6",
|
||||
|
||||
@@ -48,7 +48,9 @@ export const EventsList = (props: {events: models.Event[]}) => {
|
||||
<div className={`argo-table-list__row events-list__event events-list__event--${event.type}`} key={event.metadata.uid}>
|
||||
<div className='row'>
|
||||
<div className='columns small-2 xxlarge-2'>{event.reason}</div>
|
||||
<div className='columns small-4 xxlarge-5'>{event.message}</div>
|
||||
<div className='columns small-4 xxlarge-5' style={{whiteSpace: 'normal'}}>
|
||||
{event.message}
|
||||
</div>
|
||||
<div className='columns small-2 xxlarge-1'>{event.count}</div>
|
||||
<div className='columns small-2 xxlarge-2'>{event.firstTimestamp ? getTimeElements(event.firstTimestamp) : getTimeElements(event.eventTime)}</div>
|
||||
<div className='columns small-2 xxlarge-2'>{event.lastTimestamp ? getTimeElements(event.lastTimestamp) : getTimeElements(event.eventTime)}</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as path from 'path';
|
||||
import * as superagent from 'superagent';
|
||||
const superagentPromise = require('superagent-promise');
|
||||
import * as agent from 'superagent';
|
||||
|
||||
import {BehaviorSubject, Observable, Observer} from 'rxjs';
|
||||
import {filter} from 'rxjs/operators';
|
||||
|
||||
@@ -22,11 +22,9 @@ enum ReadyState {
|
||||
DONE = 4
|
||||
}
|
||||
|
||||
const agent: superagent.SuperAgentStatic = superagentPromise(superagent, global.Promise);
|
||||
|
||||
let baseHRef = '/';
|
||||
|
||||
const onError = new BehaviorSubject<superagent.ResponseError>(null);
|
||||
const onError = new BehaviorSubject<agent.ResponseError>(null);
|
||||
|
||||
function toAbsURL(val: string): string {
|
||||
return path.join(baseHRef, val);
|
||||
@@ -36,7 +34,7 @@ function apiRoot(): string {
|
||||
return toAbsURL('/api/v1');
|
||||
}
|
||||
|
||||
function initHandlers(req: superagent.Request) {
|
||||
function initHandlers(req: agent.Request) {
|
||||
req.on('error', err => onError.next(err));
|
||||
return req;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {Tooltip} from 'argo-ui';
|
||||
import * as React from 'react';
|
||||
import {combineLatest} from 'rxjs';
|
||||
import {map} from 'rxjs/operators';
|
||||
import {ExternalLink} from '../applications/components/application-urls';
|
||||
|
||||
import {DataLoader} from '../shared/components';
|
||||
import {services, ViewPreferences} from '../shared/services';
|
||||
@@ -66,6 +68,14 @@ export const Banner = (props: React.Props<any>) => {
|
||||
chatBottomPosition = 85;
|
||||
}
|
||||
}
|
||||
if (chatUrl) {
|
||||
try {
|
||||
const externalLink = new ExternalLink(chatUrl);
|
||||
chatUrl = externalLink.ref;
|
||||
} catch (InvalidExternalLinkError) {
|
||||
chatUrl = 'invalid-url';
|
||||
}
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className={combinedBannerClassName} style={{visibility: show ? 'visible' : 'hidden', height: heightOfBanner}}>
|
||||
@@ -96,9 +106,17 @@ export const Banner = (props: React.Props<any>) => {
|
||||
{show ? <div className={wrapperClassname}>{props.children}</div> : props.children}
|
||||
{chatUrl && (
|
||||
<div style={{position: 'fixed', right: 10, bottom: chatBottomPosition}}>
|
||||
<a href={chatUrl} className='argo-button argo-button--special'>
|
||||
<i className='fas fa-comment-alt' /> {chatText}
|
||||
</a>
|
||||
{chatUrl === 'invalid-url' ? (
|
||||
<Tooltip content='Invalid URL provided'>
|
||||
<a className='argo-button disabled argo-button--special'>
|
||||
<i className='fas fa-comment-alt' /> {chatText}
|
||||
</a>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<a href={chatUrl} className='argo-button argo-button--special'>
|
||||
<i className='fas fa-comment-alt' /> {chatText}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
||||
159
ui/yarn.lock
159
ui/yarn.lock
@@ -1885,10 +1885,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
|
||||
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
|
||||
|
||||
"@types/superagent@^3.5.7":
|
||||
version "3.8.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-3.8.7.tgz#1f1ed44634d5459b3a672eb7235a8e7cfd97704c"
|
||||
integrity sha512-9KhCkyXv268A2nZ1Wvu7rQWM+BmdYUVkycFeNnYrUL5Zwu7o8wPQ3wBfW59dDP+wuoxw0ww8YKgTNv8j/cgscA==
|
||||
"@types/superagent@^4.1.15":
|
||||
version "4.1.15"
|
||||
resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-4.1.15.tgz#63297de457eba5e2bc502a7609426c4cceab434a"
|
||||
integrity sha512-mu/N4uvfDN2zVQQ5AYJI/g4qxn2bHB6521t1UuH09ShNWjebTqN0ZFuYK9uYjcgmI0dTQEs+Owi1EO6U0OkOZQ==
|
||||
dependencies:
|
||||
"@types/cookiejar" "*"
|
||||
"@types/node" "*"
|
||||
@@ -2407,6 +2407,11 @@ array-unique@^0.3.2:
|
||||
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
|
||||
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
|
||||
|
||||
asap@^2.0.0:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
|
||||
integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==
|
||||
|
||||
assign-symbols@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
|
||||
@@ -3062,7 +3067,7 @@ colorette@^2.0.10, colorette@^2.0.14:
|
||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da"
|
||||
integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==
|
||||
|
||||
combined-stream@^1.0.6, combined-stream@^1.0.8:
|
||||
combined-stream@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||
@@ -3099,7 +3104,7 @@ commondir@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
|
||||
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
|
||||
|
||||
component-emitter@^1.2.0, component-emitter@^1.2.1:
|
||||
component-emitter@^1.2.1, component-emitter@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
|
||||
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
|
||||
@@ -3168,10 +3173,10 @@ cookie@0.4.0:
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
|
||||
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
|
||||
|
||||
cookiejar@^2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c"
|
||||
integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==
|
||||
cookiejar@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc"
|
||||
integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==
|
||||
|
||||
copy-descriptor@^0.1.0:
|
||||
version "0.1.1"
|
||||
@@ -3373,13 +3378,20 @@ debug@4, debug@^4.1.0, debug@^4.1.1:
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@^3.1.0, debug@^3.1.1:
|
||||
debug@^3.1.1:
|
||||
version "3.2.7"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
|
||||
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@^4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
decimal.js@^10.2.1:
|
||||
version "10.3.1"
|
||||
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783"
|
||||
@@ -3512,6 +3524,14 @@ detect-node@^2.0.4:
|
||||
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
|
||||
integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
|
||||
|
||||
dezalgo@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81"
|
||||
integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==
|
||||
dependencies:
|
||||
asap "^2.0.0"
|
||||
wrappy "1"
|
||||
|
||||
diff-match-patch@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37"
|
||||
@@ -4109,11 +4129,6 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
|
||||
assign-symbols "^1.0.0"
|
||||
is-extendable "^1.0.1"
|
||||
|
||||
extend@^3.0.0:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
||||
|
||||
extglob@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543"
|
||||
@@ -4164,6 +4179,11 @@ fast-safe-stringify@^2.0.7:
|
||||
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.8.tgz#dc2af48c46cf712b683e849b2bbd446b32de936f"
|
||||
integrity sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag==
|
||||
|
||||
fast-safe-stringify@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884"
|
||||
integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
|
||||
|
||||
fastest-levenshtein@^1.0.12:
|
||||
version "1.0.12"
|
||||
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2"
|
||||
@@ -4283,15 +4303,6 @@ foreach@^2.0.4:
|
||||
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
|
||||
integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k=
|
||||
|
||||
form-data@^2.3.1:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
|
||||
integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.6"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
|
||||
@@ -4301,10 +4312,24 @@ form-data@^3.0.0:
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
formidable@^1.2.0:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9"
|
||||
integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
formidable@^2.0.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/formidable/-/formidable-2.1.1.tgz#81269cbea1a613240049f5f61a9d97731517414f"
|
||||
integrity sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==
|
||||
dependencies:
|
||||
dezalgo "^1.0.4"
|
||||
hexoid "^1.0.0"
|
||||
once "^1.4.0"
|
||||
qs "^6.11.0"
|
||||
|
||||
forwarded@0.2.0:
|
||||
version "0.2.0"
|
||||
@@ -4582,6 +4607,11 @@ he@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
hexoid@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18"
|
||||
integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==
|
||||
|
||||
history@^4.10.1, history@^4.7.2:
|
||||
version "4.10.1"
|
||||
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
|
||||
@@ -6158,7 +6188,7 @@ merge2@^1.3.0:
|
||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||
|
||||
methods@^1.1.1, methods@~1.1.2:
|
||||
methods@^1.1.2, methods@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
|
||||
@@ -6222,11 +6252,16 @@ mime-types@^2.1.27, mime-types@^2.1.31:
|
||||
dependencies:
|
||||
mime-db "1.52.0"
|
||||
|
||||
mime@1.6.0, mime@^1.4.1:
|
||||
mime@1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||
|
||||
mime@^2.5.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
|
||||
integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
|
||||
|
||||
mimic-fn@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||
@@ -6365,7 +6400,7 @@ ms@2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
||||
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
|
||||
|
||||
ms@^2.1.1:
|
||||
ms@2.1.2, ms@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
@@ -7204,10 +7239,17 @@ qs@6.7.0:
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||
|
||||
qs@^6.5.1, qs@^6.9.4:
|
||||
version "6.10.1"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a"
|
||||
integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==
|
||||
qs@^6.10.3:
|
||||
version "6.10.3"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
|
||||
integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==
|
||||
dependencies:
|
||||
side-channel "^1.0.4"
|
||||
|
||||
qs@^6.11.0, qs@^6.9.4:
|
||||
version "6.11.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
|
||||
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
|
||||
dependencies:
|
||||
side-channel "^1.0.4"
|
||||
|
||||
@@ -7943,7 +7985,7 @@ read-pkg@^3.0.0:
|
||||
normalize-package-data "^2.3.2"
|
||||
path-type "^3.0.0"
|
||||
|
||||
readable-stream@^2.0.1, readable-stream@^2.3.5:
|
||||
readable-stream@^2.0.1:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
|
||||
@@ -7956,7 +7998,7 @@ readable-stream@^2.0.1, readable-stream@^2.3.5:
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readable-stream@^3.0.6:
|
||||
readable-stream@^3.0.6, readable-stream@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
||||
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
|
||||
@@ -8420,6 +8462,13 @@ semver@^6.0.0, semver@^6.3.0:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||
|
||||
semver@^7.3.7:
|
||||
version "7.3.7"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
|
||||
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
send@0.17.1:
|
||||
version "0.17.1"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
|
||||
@@ -8998,26 +9047,22 @@ style-loader@^3.3.1:
|
||||
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575"
|
||||
integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==
|
||||
|
||||
superagent-promise@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/superagent-promise/-/superagent-promise-1.1.0.tgz#baf22d8bbdd439a9b07dd10f8c08f54fe2503533"
|
||||
integrity sha1-uvIti73UOamwfdEPjAj1T+JQNTM=
|
||||
|
||||
superagent@^3.8.2:
|
||||
version "3.8.3"
|
||||
resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128"
|
||||
integrity sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==
|
||||
superagent@^7.1.3:
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/superagent/-/superagent-7.1.3.tgz#783ff8330e7c2dad6ad8f0095edc772999273b6b"
|
||||
integrity sha512-WA6et4nAvgBCS73lJvv1D0ssI5uk5Gh+TGN/kNe+B608EtcVs/yzfl+OLXTzDs7tOBDIpvgh/WUs1K2OK1zTeQ==
|
||||
dependencies:
|
||||
component-emitter "^1.2.0"
|
||||
cookiejar "^2.1.0"
|
||||
debug "^3.1.0"
|
||||
extend "^3.0.0"
|
||||
form-data "^2.3.1"
|
||||
formidable "^1.2.0"
|
||||
methods "^1.1.1"
|
||||
mime "^1.4.1"
|
||||
qs "^6.5.1"
|
||||
readable-stream "^2.3.5"
|
||||
component-emitter "^1.3.0"
|
||||
cookiejar "^2.1.3"
|
||||
debug "^4.3.4"
|
||||
fast-safe-stringify "^2.1.1"
|
||||
form-data "^4.0.0"
|
||||
formidable "^2.0.1"
|
||||
methods "^1.1.2"
|
||||
mime "^2.5.0"
|
||||
qs "^6.10.3"
|
||||
readable-stream "^3.6.0"
|
||||
semver "^7.3.7"
|
||||
|
||||
supports-color@^2.0.0:
|
||||
version "2.0.0"
|
||||
|
||||
@@ -16,7 +16,7 @@ spec:
|
||||
terminationGracePeriodSeconds: 10
|
||||
containers:
|
||||
- name: nginx
|
||||
image: k8s.gcr.io/nginx-slim:0.8
|
||||
image: registry.k8s.io/nginx-slim:0.8
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: web
|
||||
|
||||
@@ -348,9 +348,12 @@ func (a *ClientApp) HandleCallback(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "no id_token in token response", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
idToken, err := a.provider.Verify(a.clientID, idTokenRAW)
|
||||
|
||||
idToken, err := a.provider.Verify(idTokenRAW, a.settings)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("invalid session token: %v", err), http.StatusInternalServerError)
|
||||
log.Warnf("Failed to verify token: %s", err)
|
||||
http.Error(w, common.TokenVerificationError, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
path := "/"
|
||||
|
||||
@@ -89,7 +89,7 @@ func (p *fakeProvider) ParseConfig() (*OIDCConfiguration, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *fakeProvider) Verify(_, _ string) (*gooidc.IDToken, error) {
|
||||
func (p *fakeProvider) Verify(_ string, _ *settings.ArgoCDSettings) (*gooidc.IDToken, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -9,8 +10,13 @@ import (
|
||||
gooidc "github.com/coreos/go-oidc"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/util/security"
|
||||
"github.com/argoproj/argo-cd/v2/util/settings"
|
||||
)
|
||||
|
||||
var ErrTokenExpired = errors.New("token is expired")
|
||||
|
||||
// Provider is a wrapper around go-oidc provider to also provide the following features:
|
||||
// 1. lazy initialization/querying of the provider
|
||||
// 2. automatic detection of change in signing keys
|
||||
@@ -23,7 +29,7 @@ type Provider interface {
|
||||
|
||||
ParseConfig() (*OIDCConfiguration, error)
|
||||
|
||||
Verify(clientID, tokenString string) (*gooidc.IDToken, error)
|
||||
Verify(tokenString string, argoSettings *settings.ArgoCDSettings) (*gooidc.IDToken, error)
|
||||
}
|
||||
|
||||
type providerImpl struct {
|
||||
@@ -69,13 +75,70 @@ func (p *providerImpl) newGoOIDCProvider() (*gooidc.Provider, error) {
|
||||
return prov, nil
|
||||
}
|
||||
|
||||
func (p *providerImpl) Verify(clientID, tokenString string) (*gooidc.IDToken, error) {
|
||||
func (p *providerImpl) Verify(tokenString string, argoSettings *settings.ArgoCDSettings) (*gooidc.IDToken, error) {
|
||||
// According to the JWT spec, the aud claim is optional. The spec also says (emphasis mine):
|
||||
//
|
||||
// If the principal processing the claim does not identify itself with a value in the "aud" claim _when this
|
||||
// claim is present_, then the JWT MUST be rejected.
|
||||
//
|
||||
// - https://www.rfc-editor.org/rfc/rfc7519#section-4.1.3
|
||||
//
|
||||
// If the claim is not present, we can skip the audience claim check (called the "ClientID check" in go-oidc's
|
||||
// terminology).
|
||||
//
|
||||
// The OIDC spec says that the aud claim is required (https://openid.net/specs/openid-connect-core-1_0.html#IDToken).
|
||||
// But we cannot assume that all OIDC providers will follow the spec. For Argo CD <2.6.0, we will default to
|
||||
// allowing the aud claim to be optional. In Argo CD >=2.6.0, we will default to requiring the aud claim to be
|
||||
// present and give users the skipAudienceCheckWhenTokenHasNoAudience setting to revert the behavior if necessary.
|
||||
//
|
||||
// At this point, we have not verified that the token has not been altered. All code paths below MUST VERIFY
|
||||
// THE TOKEN SIGNATURE to confirm that an attacker did not maliciously remove the "aud" claim.
|
||||
unverifiedHasAudClaim, err := security.UnverifiedHasAudClaim(tokenString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to determine whether the token has an aud claim: %w", err)
|
||||
}
|
||||
|
||||
var idToken *gooidc.IDToken
|
||||
if !unverifiedHasAudClaim {
|
||||
idToken, err = p.verify("", tokenString, argoSettings.SkipAudienceCheckWhenTokenHasNoAudience())
|
||||
} else {
|
||||
allowedAudiences := argoSettings.OAuth2AllowedAudiences()
|
||||
if len(allowedAudiences) == 0 {
|
||||
return nil, errors.New("token has an audience claim, but no allowed audiences are configured")
|
||||
}
|
||||
// Token must be verified for at least one allowed audience
|
||||
for _, aud := range allowedAudiences {
|
||||
idToken, err = p.verify(aud, tokenString, false)
|
||||
if err != nil && strings.HasPrefix(err.Error(), "oidc: token is expired") {
|
||||
// If the token is expired, we won't bother checking other audiences. It's important to return a
|
||||
// ErrTokenExpired instead of an error related to an incorrect audience, because the caller may
|
||||
// have specific behavior to handle expired tokens.
|
||||
break
|
||||
}
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if strings.HasPrefix(err.Error(), "oidc: token is expired") {
|
||||
return nil, ErrTokenExpired
|
||||
}
|
||||
return nil, fmt.Errorf("failed to verify token: %w", err)
|
||||
}
|
||||
|
||||
return idToken, nil
|
||||
}
|
||||
|
||||
func (p *providerImpl) verify(clientID, tokenString string, skipClientIDCheck bool) (*gooidc.IDToken, error) {
|
||||
ctx := context.Background()
|
||||
prov, err := p.provider()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
verifier := prov.Verifier(&gooidc.Config{ClientID: clientID})
|
||||
config := &gooidc.Config{ClientID: clientID, SkipClientIDCheck: skipClientIDCheck}
|
||||
verifier := prov.Verifier(config)
|
||||
idToken, err := verifier.Verify(ctx, tokenString)
|
||||
if err != nil {
|
||||
// HACK: if we failed token verification, it's possible the reason was because dex
|
||||
@@ -93,7 +156,7 @@ func (p *providerImpl) Verify(clientID, tokenString string) (*gooidc.IDToken, er
|
||||
// return original error if we fail to re-initialize OIDC
|
||||
return nil, err
|
||||
}
|
||||
verifier = newProvider.Verifier(&gooidc.Config{ClientID: clientID})
|
||||
verifier = newProvider.Verifier(config)
|
||||
idToken, err = verifier.Verify(ctx, tokenString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
80
util/security/jwt.go
Normal file
80
util/security/jwt.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// parseJWT parses a jwt and returns it as json bytes.
|
||||
//
|
||||
// This function DOES NOT VERIFY THE TOKEN. You still have to verify the token to confirm that the token holder has not
|
||||
// altered the claims.
|
||||
//
|
||||
// This code is copied almost verbatim from go-oidc (https://github.com/coreos/go-oidc).
|
||||
func parseJWT(p string) ([]byte, error) {
|
||||
parts := strings.Split(p, ".")
|
||||
if len(parts) < 2 {
|
||||
return nil, fmt.Errorf("malformed jwt, expected 3 parts got %d", len(parts))
|
||||
}
|
||||
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed jwt payload: %v", err)
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
type audience []string
|
||||
|
||||
// UnmarshalJSON allows us to unmarshal either a single audience or a list of audiences.
|
||||
// Taken from: https://github.com/coreos/go-oidc/blob/a8ceb9a2043fca2e43518633920db746808b1138/oidc/oidc.go#L475
|
||||
func (a *audience) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
if json.Unmarshal(b, &s) == nil {
|
||||
*a = audience{s}
|
||||
return nil
|
||||
}
|
||||
var auds []string
|
||||
if err := json.Unmarshal(b, &auds); err != nil {
|
||||
return err
|
||||
}
|
||||
*a = auds
|
||||
return nil
|
||||
}
|
||||
|
||||
// jwtWithOnlyAudClaim represents a jwt where only the "aud" claim is present. This struct allows us to unmarshal a jwt
|
||||
// and be confident that the only information retrieved from that jwt is the "aud" claim.
|
||||
type jwtWithOnlyAudClaim struct {
|
||||
Aud audience `json:"aud"`
|
||||
}
|
||||
|
||||
// getUnverifiedAudClaim gets the "aud" claim from a jwt.
|
||||
//
|
||||
// This function DOES NOT VERIFY THE TOKEN. You still have to verify the token to confirm that the token holder has not
|
||||
// altered the "aud" claim.
|
||||
//
|
||||
// This code is copied almost verbatim from go-oidc (https://github.com/coreos/go-oidc).
|
||||
func getUnverifiedAudClaim(rawIDToken string) ([]string, error) {
|
||||
payload, err := parseJWT(rawIDToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed jwt: %v", err)
|
||||
}
|
||||
var token jwtWithOnlyAudClaim
|
||||
if err = json.Unmarshal(payload, &token); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal claims: %v", err)
|
||||
}
|
||||
return token.Aud, nil
|
||||
}
|
||||
|
||||
// UnverifiedHasAudClaim returns whether the "aud" claim is present in the given JWT.
|
||||
//
|
||||
// This function DOES NOT VERIFY THE TOKEN. You still have to verify the token to confirm that the token holder has not
|
||||
// altered the "aud" claim.
|
||||
func UnverifiedHasAudClaim(rawIDToken string) (bool, error) {
|
||||
aud, err := getUnverifiedAudClaim(rawIDToken)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to determine whether token had an audience claim: %w", err)
|
||||
}
|
||||
return aud != nil, nil
|
||||
}
|
||||
56
util/security/jwt_test.go
Normal file
56
util/security/jwt_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
utiltest "github.com/argoproj/argo-cd/v2/util/test"
|
||||
)
|
||||
|
||||
func Test_UnverifiedHasAudClaim(t *testing.T) {
|
||||
tokenForAud := func(t *testing.T, aud jwt.ClaimStrings) string {
|
||||
claims := jwt.RegisteredClaims{Audience: aud, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
tokenString, err := token.SignedString(key)
|
||||
require.NoError(t, err)
|
||||
return tokenString
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
aud jwt.ClaimStrings
|
||||
expectedHasAud bool
|
||||
}{
|
||||
{
|
||||
name: "no audience",
|
||||
aud: jwt.ClaimStrings{},
|
||||
expectedHasAud: false,
|
||||
},
|
||||
{
|
||||
name: "one empty audience",
|
||||
aud: jwt.ClaimStrings{""},
|
||||
expectedHasAud: true,
|
||||
},
|
||||
{
|
||||
name: "one non-empty audience",
|
||||
aud: jwt.ClaimStrings{"test"},
|
||||
expectedHasAud: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
testCaseCopy := testCase
|
||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
out, err := UnverifiedHasAudClaim(tokenForAud(t, testCaseCopy.aud))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, testCaseCopy.expectedHasAud, out)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
oidc "github.com/coreos/go-oidc"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -413,7 +412,7 @@ func (mgr *SessionManager) VerifyUsernamePassword(username string, password stri
|
||||
// introduces random delay to protect from timing-based user enumeration attack
|
||||
delayNanoseconds := verificationDelayNoiseMin.Nanoseconds() +
|
||||
int64(rand.Intn(int(verificationDelayNoiseMax.Nanoseconds()-verificationDelayNoiseMin.Nanoseconds())))
|
||||
// take into account amount of time spent since the request start
|
||||
// take into account amount of time spent since the request start
|
||||
delayNanoseconds = delayNanoseconds - time.Since(start).Nanoseconds()
|
||||
if delayNanoseconds > 0 {
|
||||
mgr.sleep(time.Duration(delayNanoseconds))
|
||||
@@ -457,6 +456,47 @@ func (mgr *SessionManager) VerifyUsernamePassword(username string, password stri
|
||||
return nil
|
||||
}
|
||||
|
||||
// AuthMiddlewareFunc returns a function that can be used as an
|
||||
// authentication middleware for HTTP requests.
|
||||
func (mgr *SessionManager) AuthMiddlewareFunc(disabled bool) func(http.Handler) http.Handler {
|
||||
return func(h http.Handler) http.Handler {
|
||||
return WithAuthMiddleware(disabled, mgr, h)
|
||||
}
|
||||
}
|
||||
|
||||
// TokenVerifier defines the contract to invoke token
|
||||
// verification logic
|
||||
type TokenVerifier interface {
|
||||
VerifyToken(token string) (jwt.Claims, string, error)
|
||||
}
|
||||
|
||||
// WithAuthMiddleware is an HTTP middleware used to ensure incoming
|
||||
// requests are authenticated before invoking the target handler. If
|
||||
// disabled is true, it will just invoke the next handler in the chain.
|
||||
func WithAuthMiddleware(disabled bool, authn TokenVerifier, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !disabled {
|
||||
cookies := r.Cookies()
|
||||
tokenString, err := httputil.JoinCookies(common.AuthCookieName, cookies)
|
||||
if err != nil {
|
||||
http.Error(w, "Auth cookie not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
claims, _, err := authn.VerifyToken(tokenString)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
// Add claims to the context to inspect for RBAC
|
||||
// nolint:staticcheck
|
||||
ctx = context.WithValue(ctx, "claims", claims)
|
||||
r = r.WithContext(ctx)
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// VerifyToken verifies if a token is correct. Tokens can be issued either from us or by an IDP.
|
||||
// We choose how to verify based on the issuer.
|
||||
func (mgr *SessionManager) VerifyToken(tokenString string) (jwt.Claims, string, error) {
|
||||
@@ -477,31 +517,28 @@ func (mgr *SessionManager) VerifyToken(tokenString string) (jwt.Claims, string,
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Token must be verified for at least one audience
|
||||
// TODO(jannfis): Is this the right way? Shouldn't we know our audience and only validate for the correct one?
|
||||
var idToken *oidc.IDToken
|
||||
for _, aud := range claims.Audience {
|
||||
idToken, err = prov.Verify(aud, tokenString)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
argoSettings, err := mgr.settingsMgr.GetSettings()
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("cannot access settings while verifying the token: %w", err)
|
||||
}
|
||||
if argoSettings == nil {
|
||||
return nil, "", fmt.Errorf("settings are not available while verifying the token")
|
||||
}
|
||||
|
||||
idToken, err := prov.Verify(tokenString, argoSettings)
|
||||
|
||||
// The token verification has failed. If the token has expired, we will
|
||||
// return a dummy claims only containing a value for the issuer, so the
|
||||
// UI can handle expired tokens appropriately.
|
||||
if err != nil {
|
||||
if strings.HasPrefix(err.Error(), "oidc: token is expired") {
|
||||
log.Warnf("Failed to verify token: %s", err)
|
||||
if errors.Is(err, oidcutil.ErrTokenExpired) {
|
||||
claims = jwt.RegisteredClaims{
|
||||
Issuer: "sso",
|
||||
}
|
||||
return claims, "", err
|
||||
return claims, "", common.TokenVerificationErr
|
||||
}
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if idToken == nil {
|
||||
return nil, "", fmt.Errorf("no audience found in the token")
|
||||
return nil, "", common.TokenVerificationErr
|
||||
}
|
||||
|
||||
var claims jwt.MapClaims
|
||||
|
||||
@@ -3,8 +3,12 @@ package session
|
||||
import (
|
||||
"context"
|
||||
"encoding/pem"
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -221,6 +225,136 @@ func TestSessionManager_ProjectToken(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
type claimsMock struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (cm *claimsMock) Valid() error {
|
||||
return cm.err
|
||||
}
|
||||
|
||||
type tokenVerifierMock struct {
|
||||
claims *claimsMock
|
||||
err error
|
||||
}
|
||||
|
||||
func (tm *tokenVerifierMock) VerifyToken(token string) (jwt.Claims, string, error) {
|
||||
if tm.claims == nil {
|
||||
return nil, "", tm.err
|
||||
}
|
||||
return tm.claims, "", tm.err
|
||||
}
|
||||
|
||||
func strPointer(str string) *string {
|
||||
return &str
|
||||
}
|
||||
|
||||
func TestSessionManager_WithAuthMiddleware(t *testing.T) {
|
||||
handlerFunc := func() func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Helper()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", "application/text")
|
||||
_, err := w.Write([]byte("Ok"))
|
||||
if err != nil {
|
||||
t.Fatalf("error writing response: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
type testCase struct {
|
||||
name string
|
||||
authDisabled bool
|
||||
cookieHeader bool
|
||||
verifiedClaims *claimsMock
|
||||
verifyTokenErr error
|
||||
expectedStatusCode int
|
||||
expectedResponseBody *string
|
||||
}
|
||||
|
||||
cases := []testCase{
|
||||
{
|
||||
name: "will authenticate successfully",
|
||||
authDisabled: false,
|
||||
cookieHeader: true,
|
||||
verifiedClaims: &claimsMock{},
|
||||
verifyTokenErr: nil,
|
||||
expectedStatusCode: 200,
|
||||
expectedResponseBody: strPointer("Ok"),
|
||||
},
|
||||
{
|
||||
name: "will be noop if auth is disabled",
|
||||
authDisabled: true,
|
||||
cookieHeader: false,
|
||||
verifiedClaims: nil,
|
||||
verifyTokenErr: nil,
|
||||
expectedStatusCode: 200,
|
||||
expectedResponseBody: strPointer("Ok"),
|
||||
},
|
||||
{
|
||||
name: "will return 400 if no cookie header",
|
||||
authDisabled: false,
|
||||
cookieHeader: false,
|
||||
verifiedClaims: &claimsMock{},
|
||||
verifyTokenErr: nil,
|
||||
expectedStatusCode: 400,
|
||||
expectedResponseBody: nil,
|
||||
},
|
||||
{
|
||||
name: "will return 401 verify token fails",
|
||||
authDisabled: false,
|
||||
cookieHeader: true,
|
||||
verifiedClaims: &claimsMock{},
|
||||
verifyTokenErr: stderrors.New("token error"),
|
||||
expectedStatusCode: 401,
|
||||
expectedResponseBody: nil,
|
||||
},
|
||||
{
|
||||
name: "will return 200 if claims are nil",
|
||||
authDisabled: false,
|
||||
cookieHeader: true,
|
||||
verifiedClaims: nil,
|
||||
verifyTokenErr: nil,
|
||||
expectedStatusCode: 200,
|
||||
expectedResponseBody: strPointer("Ok"),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// given
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", handlerFunc())
|
||||
tm := &tokenVerifierMock{
|
||||
claims: tc.verifiedClaims,
|
||||
err: tc.verifyTokenErr,
|
||||
}
|
||||
ts := httptest.NewServer(WithAuthMiddleware(tc.authDisabled, tm, mux))
|
||||
defer ts.Close()
|
||||
req, err := http.NewRequest(http.MethodGet, ts.URL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating request: %s", err)
|
||||
}
|
||||
if tc.cookieHeader {
|
||||
req.Header.Add("Cookie", "argocd.token=123456")
|
||||
}
|
||||
|
||||
// when
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.Equal(t, tc.expectedStatusCode, resp.StatusCode)
|
||||
if tc.expectedResponseBody != nil {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
actual := strings.TrimSuffix(string(body), "\n")
|
||||
assert.Contains(t, actual, *tc.expectedResponseBody)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var loggedOutContext = context.Background()
|
||||
|
||||
// nolint:staticcheck
|
||||
@@ -588,9 +722,7 @@ rootCA: |
|
||||
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
require.Error(t, err)
|
||||
if !strings.Contains(err.Error(), "certificate signed by unknown authority") && !strings.Contains(err.Error(), "certificate is not trusted") {
|
||||
t.Fatal("did not receive expected certificate verification failure error")
|
||||
}
|
||||
assert.ErrorIs(t, err, common.TokenVerificationErr)
|
||||
})
|
||||
|
||||
t.Run("OIDC provider is external, TLS is configured", func(t *testing.T) {
|
||||
@@ -625,9 +757,7 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
|
||||
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
require.Error(t, err)
|
||||
if !strings.Contains(err.Error(), "certificate signed by unknown authority") && !strings.Contains(err.Error(), "certificate is not trusted") {
|
||||
t.Fatal("did not receive expected certificate verification failure error")
|
||||
}
|
||||
assert.ErrorIs(t, err, common.TokenVerificationErr)
|
||||
})
|
||||
|
||||
t.Run("OIDC provider is Dex, TLS is configured", func(t *testing.T) {
|
||||
@@ -662,9 +792,7 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
|
||||
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
require.Error(t, err)
|
||||
if !strings.Contains(err.Error(), "certificate signed by unknown authority") && !strings.Contains(err.Error(), "certificate is not trusted") {
|
||||
t.Fatal("did not receive expected certificate verification failure error")
|
||||
}
|
||||
assert.ErrorIs(t, err, common.TokenVerificationErr)
|
||||
})
|
||||
|
||||
t.Run("OIDC provider is external, TLS is configured, OIDCTLSInsecureSkipVerify is true", func(t *testing.T) {
|
||||
@@ -732,4 +860,295 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
|
||||
assert.NotContains(t, err.Error(), "certificate is not trusted")
|
||||
assert.NotContains(t, err.Error(), "certificate signed by unknown authority")
|
||||
})
|
||||
|
||||
t.Run("OIDC provider is external, audience is not specified", func(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"url": "",
|
||||
"oidc.config": fmt.Sprintf(`
|
||||
name: Test
|
||||
issuer: %s
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]`, oidcTestServer.URL),
|
||||
"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
|
||||
}
|
||||
|
||||
// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
|
||||
// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
|
||||
secretConfig := map[string][]byte{
|
||||
"tls.crt": utiltest.Cert,
|
||||
"tls.key": utiltest.PrivateKey,
|
||||
}
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClientWithConfig(config, secretConfig), "argocd")
|
||||
mgr := NewSessionManager(settingsMgr, getProjLister(), "", NewUserStateStorage(nil))
|
||||
mgr.verificationDelayNoiseEnabled = false
|
||||
|
||||
claims := jwt.RegisteredClaims{Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
|
||||
claims.Issuer = oidcTestServer.URL
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
tokenString, err := token.SignedString(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("OIDC provider is external, audience is not specified but is required", func(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"url": "",
|
||||
"oidc.config": fmt.Sprintf(`
|
||||
name: Test
|
||||
issuer: %s
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]
|
||||
skipAudienceCheckWhenTokenHasNoAudience: false`, oidcTestServer.URL),
|
||||
"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
|
||||
}
|
||||
|
||||
// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
|
||||
// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
|
||||
secretConfig := map[string][]byte{
|
||||
"tls.crt": utiltest.Cert,
|
||||
"tls.key": utiltest.PrivateKey,
|
||||
}
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClientWithConfig(config, secretConfig), "argocd")
|
||||
mgr := NewSessionManager(settingsMgr, getProjLister(), "", NewUserStateStorage(nil))
|
||||
mgr.verificationDelayNoiseEnabled = false
|
||||
|
||||
claims := jwt.RegisteredClaims{Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
|
||||
claims.Issuer = oidcTestServer.URL
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
tokenString, err := token.SignedString(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, common.TokenVerificationErr)
|
||||
})
|
||||
|
||||
t.Run("OIDC provider is external, audience is client ID, no allowed list specified", func(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"url": "",
|
||||
"oidc.config": fmt.Sprintf(`
|
||||
name: Test
|
||||
issuer: %s
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]`, oidcTestServer.URL),
|
||||
"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
|
||||
}
|
||||
|
||||
// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
|
||||
// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
|
||||
secretConfig := map[string][]byte{
|
||||
"tls.crt": utiltest.Cert,
|
||||
"tls.key": utiltest.PrivateKey,
|
||||
}
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClientWithConfig(config, secretConfig), "argocd")
|
||||
mgr := NewSessionManager(settingsMgr, getProjLister(), "", NewUserStateStorage(nil))
|
||||
mgr.verificationDelayNoiseEnabled = false
|
||||
|
||||
claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"xxx"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
|
||||
claims.Issuer = oidcTestServer.URL
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
tokenString, err := token.SignedString(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("OIDC provider is external, audience is in allowed list", func(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"url": "",
|
||||
"oidc.config": fmt.Sprintf(`
|
||||
name: Test
|
||||
issuer: %s
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]
|
||||
allowedAudiences:
|
||||
- something`, oidcTestServer.URL),
|
||||
"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
|
||||
}
|
||||
|
||||
// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
|
||||
// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
|
||||
secretConfig := map[string][]byte{
|
||||
"tls.crt": utiltest.Cert,
|
||||
"tls.key": utiltest.PrivateKey,
|
||||
}
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClientWithConfig(config, secretConfig), "argocd")
|
||||
mgr := NewSessionManager(settingsMgr, getProjLister(), "", NewUserStateStorage(nil))
|
||||
mgr.verificationDelayNoiseEnabled = false
|
||||
|
||||
claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"something"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
|
||||
claims.Issuer = oidcTestServer.URL
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
tokenString, err := token.SignedString(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("OIDC provider is external, audience is not in allowed list", func(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"url": "",
|
||||
"oidc.config": fmt.Sprintf(`
|
||||
name: Test
|
||||
issuer: %s
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]
|
||||
allowedAudiences:
|
||||
- something-else`, oidcTestServer.URL),
|
||||
"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
|
||||
}
|
||||
|
||||
// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
|
||||
// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
|
||||
secretConfig := map[string][]byte{
|
||||
"tls.crt": utiltest.Cert,
|
||||
"tls.key": utiltest.PrivateKey,
|
||||
}
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClientWithConfig(config, secretConfig), "argocd")
|
||||
mgr := NewSessionManager(settingsMgr, getProjLister(), "", NewUserStateStorage(nil))
|
||||
mgr.verificationDelayNoiseEnabled = false
|
||||
|
||||
claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"something"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
|
||||
claims.Issuer = oidcTestServer.URL
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
tokenString, err := token.SignedString(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, common.TokenVerificationErr)
|
||||
})
|
||||
|
||||
t.Run("OIDC provider is external, audience is not client ID, and there is no allow list", func(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"url": "",
|
||||
"oidc.config": fmt.Sprintf(`
|
||||
name: Test
|
||||
issuer: %s
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]`, oidcTestServer.URL),
|
||||
"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
|
||||
}
|
||||
|
||||
// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
|
||||
// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
|
||||
secretConfig := map[string][]byte{
|
||||
"tls.crt": utiltest.Cert,
|
||||
"tls.key": utiltest.PrivateKey,
|
||||
}
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClientWithConfig(config, secretConfig), "argocd")
|
||||
mgr := NewSessionManager(settingsMgr, getProjLister(), "", NewUserStateStorage(nil))
|
||||
mgr.verificationDelayNoiseEnabled = false
|
||||
|
||||
claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"something"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
|
||||
claims.Issuer = oidcTestServer.URL
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
tokenString, err := token.SignedString(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, common.TokenVerificationErr)
|
||||
})
|
||||
|
||||
t.Run("OIDC provider is external, audience is specified, but allow list is empty", func(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"url": "",
|
||||
"oidc.config": fmt.Sprintf(`
|
||||
name: Test
|
||||
issuer: %s
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]
|
||||
allowedAudiences: []`, oidcTestServer.URL),
|
||||
"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
|
||||
}
|
||||
|
||||
// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
|
||||
// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
|
||||
secretConfig := map[string][]byte{
|
||||
"tls.crt": utiltest.Cert,
|
||||
"tls.key": utiltest.PrivateKey,
|
||||
}
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClientWithConfig(config, secretConfig), "argocd")
|
||||
mgr := NewSessionManager(settingsMgr, getProjLister(), "", NewUserStateStorage(nil))
|
||||
mgr.verificationDelayNoiseEnabled = false
|
||||
|
||||
claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"something"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
|
||||
claims.Issuer = oidcTestServer.URL
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
tokenString, err := token.SignedString(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, common.TokenVerificationErr)
|
||||
})
|
||||
|
||||
t.Run("OIDC provider is external, audience is not specified, token is signed with the wrong key", func(t *testing.T) {
|
||||
config := map[string]string{
|
||||
"url": "",
|
||||
"oidc.config": fmt.Sprintf(`
|
||||
name: Test
|
||||
issuer: %s
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]`, oidcTestServer.URL),
|
||||
"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
|
||||
}
|
||||
|
||||
// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
|
||||
// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
|
||||
secretConfig := map[string][]byte{
|
||||
"tls.crt": utiltest.Cert,
|
||||
"tls.key": utiltest.PrivateKey,
|
||||
}
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClientWithConfig(config, secretConfig), "argocd")
|
||||
mgr := NewSessionManager(settingsMgr, getProjLister(), "", NewUserStateStorage(nil))
|
||||
mgr.verificationDelayNoiseEnabled = false
|
||||
|
||||
claims := jwt.RegisteredClaims{Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
|
||||
claims.Issuer = oidcTestServer.URL
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey2)
|
||||
require.NoError(t, err)
|
||||
tokenString, err := token.SignedString(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = mgr.VerifyToken(tokenString)
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, common.TokenVerificationErr)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -130,6 +130,33 @@ type Help struct {
|
||||
BinaryURLs map[string]string `json:"binaryUrl,omitempty"`
|
||||
}
|
||||
|
||||
// oidcConfig is the same as the public OIDCConfig, except the public one excludes the AllowedAudiences and the
|
||||
// SkipAudienceCheckWhenTokenHasNoAudience fields.
|
||||
// AllowedAudiences should be accessed via ArgoCDSettings.OAuth2AllowedAudiences.
|
||||
// SkipAudienceCheckWhenTokenHasNoAudience should be accessed via ArgoCDSettings.SkipAudienceCheckWhenTokenHasNoAudience.
|
||||
type oidcConfig struct {
|
||||
OIDCConfig
|
||||
AllowedAudiences []string `json:"allowedAudiences,omitempty"`
|
||||
SkipAudienceCheckWhenTokenHasNoAudience *bool `json:"skipAudienceCheckWhenTokenHasNoAudience,omitempty"`
|
||||
}
|
||||
|
||||
func (o *oidcConfig) toExported() *OIDCConfig {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return &OIDCConfig{
|
||||
Name: o.Name,
|
||||
Issuer: o.Issuer,
|
||||
ClientID: o.ClientID,
|
||||
ClientSecret: o.ClientSecret,
|
||||
CLIClientID: o.CLIClientID,
|
||||
RequestedScopes: o.RequestedScopes,
|
||||
RequestedIDTokenClaims: o.RequestedIDTokenClaims,
|
||||
LogoutURL: o.LogoutURL,
|
||||
RootCA: o.RootCA,
|
||||
}
|
||||
}
|
||||
|
||||
type OIDCConfig struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Issuer string `json:"issuer,omitempty"`
|
||||
@@ -1596,24 +1623,37 @@ func UnmarshalDexConfig(config string) (map[string]interface{}, error) {
|
||||
return dexCfg, err
|
||||
}
|
||||
|
||||
func (a *ArgoCDSettings) OIDCConfig() *OIDCConfig {
|
||||
func (a *ArgoCDSettings) oidcConfig() *oidcConfig {
|
||||
if a.OIDCConfigRAW == "" {
|
||||
return nil
|
||||
}
|
||||
oidcConfig, err := UnmarshalOIDCConfig(a.OIDCConfigRAW)
|
||||
config, err := unmarshalOIDCConfig(a.OIDCConfigRAW)
|
||||
if err != nil {
|
||||
log.Warnf("invalid oidc config: %v", err)
|
||||
return nil
|
||||
}
|
||||
oidcConfig.ClientSecret = ReplaceStringSecret(oidcConfig.ClientSecret, a.Secrets)
|
||||
oidcConfig.ClientID = ReplaceStringSecret(oidcConfig.ClientID, a.Secrets)
|
||||
return &oidcConfig
|
||||
config.ClientSecret = ReplaceStringSecret(config.ClientSecret, a.Secrets)
|
||||
config.ClientID = ReplaceStringSecret(config.ClientID, a.Secrets)
|
||||
return &config
|
||||
}
|
||||
|
||||
func UnmarshalOIDCConfig(config string) (OIDCConfig, error) {
|
||||
var oidcConfig OIDCConfig
|
||||
err := yaml.Unmarshal([]byte(config), &oidcConfig)
|
||||
return oidcConfig, err
|
||||
func (a *ArgoCDSettings) OIDCConfig() *OIDCConfig {
|
||||
config := a.oidcConfig()
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
return config.toExported()
|
||||
}
|
||||
|
||||
func unmarshalOIDCConfig(configStr string) (oidcConfig, error) {
|
||||
var config oidcConfig
|
||||
err := yaml.Unmarshal([]byte(configStr), &config)
|
||||
return config, err
|
||||
}
|
||||
|
||||
func ValidateOIDCConfig(configStr string) error {
|
||||
_, err := unmarshalOIDCConfig(configStr)
|
||||
return err
|
||||
}
|
||||
|
||||
// TLSConfig returns a tls.Config with the configured certificates
|
||||
@@ -1652,6 +1692,37 @@ func (a *ArgoCDSettings) OAuth2ClientID() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// OAuth2AllowedAudiences returns a list of audiences that are allowed for the OAuth2 client. If the user has not
|
||||
// explicitly configured the list of audiences (or has configured an empty list), then the OAuth2 client ID is returned
|
||||
// as the only allowed audience. When using the bundled Dex, that client ID is always "argo-cd".
|
||||
func (a *ArgoCDSettings) OAuth2AllowedAudiences() []string {
|
||||
if config := a.oidcConfig(); config != nil {
|
||||
if len(config.AllowedAudiences) == 0 {
|
||||
allowedAudiences := []string{config.ClientID}
|
||||
if config.CLIClientID != "" {
|
||||
allowedAudiences = append(allowedAudiences, config.CLIClientID)
|
||||
}
|
||||
return allowedAudiences
|
||||
}
|
||||
return config.AllowedAudiences
|
||||
}
|
||||
if a.DexConfig != "" {
|
||||
return []string{common.ArgoCDClientAppID, common.ArgoCDCLIClientAppID}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ArgoCDSettings) SkipAudienceCheckWhenTokenHasNoAudience() bool {
|
||||
if config := a.oidcConfig(); config != nil {
|
||||
if config.SkipAudienceCheckWhenTokenHasNoAudience != nil {
|
||||
return *config.SkipAudienceCheckWhenTokenHasNoAudience
|
||||
}
|
||||
return true
|
||||
}
|
||||
// When using the bundled Dex, the audience check is required. Dex will always send JWTs with an audience.
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *ArgoCDSettings) OAuth2ClientSecret() string {
|
||||
if oidcConfig := a.OIDCConfig(); oidcConfig != nil {
|
||||
return oidcConfig.ClientSecret
|
||||
|
||||
@@ -1245,3 +1245,68 @@ rootCA: "invalid"`},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_OAuth2AllowedAudiences(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
settings *ArgoCDSettings
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "Empty",
|
||||
settings: &ArgoCDSettings{},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "OIDC configured, no audiences specified, clientID used",
|
||||
settings: &ArgoCDSettings{OIDCConfigRAW: `name: Test
|
||||
issuer: aaa
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]`},
|
||||
expected: []string{"xxx"},
|
||||
},
|
||||
{
|
||||
name: "OIDC configured, no audiences specified, clientID and cliClientID used",
|
||||
settings: &ArgoCDSettings{OIDCConfigRAW: `name: Test
|
||||
issuer: aaa
|
||||
clientID: xxx
|
||||
cliClientID: cli-xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]`},
|
||||
expected: []string{"xxx", "cli-xxx"},
|
||||
},
|
||||
{
|
||||
name: "OIDC configured, audiences specified",
|
||||
settings: &ArgoCDSettings{OIDCConfigRAW: `name: Test
|
||||
issuer: aaa
|
||||
clientID: xxx
|
||||
clientSecret: yyy
|
||||
requestedScopes: ["oidc"]
|
||||
allowedAudiences: ["aud1", "aud2"]`},
|
||||
expected: []string{"aud1", "aud2"},
|
||||
},
|
||||
{
|
||||
name: "Dex configured",
|
||||
settings: &ArgoCDSettings{DexConfig: `connectors:
|
||||
- type: github
|
||||
id: github
|
||||
name: GitHub
|
||||
config:
|
||||
clientID: aabbccddeeff00112233
|
||||
clientSecret: $dex.github.clientSecret
|
||||
orgs:
|
||||
- name: your-github-org
|
||||
`},
|
||||
expected: []string{common.ArgoCDClientAppID, common.ArgoCDCLIClientAppID},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tcc := tc
|
||||
t.Run(tcc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert.ElementsMatch(t, tcc.expected, tcc.settings.OAuth2AllowedAudiences())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
// Cert is a certificate for tests. It was generated like this:
|
||||
// opts := tls.CertOptions{Hosts: []string{"localhost"}, Organization: "Acme"}
|
||||
// certBytes, privKey, err := tls.generatePEM(opts)
|
||||
//
|
||||
// opts := tls.CertOptions{Hosts: []string{"localhost"}, Organization: "Acme"}
|
||||
// certBytes, privKey, err := tls.generatePEM(opts)
|
||||
var Cert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIC8zCCAdugAwIBAgIQCSoocl6e/FR4mQy1wX6NbjANBgkqhkiG9w0BAQsFADAP
|
||||
MQ0wCwYDVQQKEwRBY21lMB4XDTIyMDYyMjE3Mjk1MloXDTIzMDYyMjE3Mjk1Mlow
|
||||
@@ -61,6 +65,48 @@ SDpz4h+Bov5qTKkzcxuu1QWtA4M0K8Iy6IYLwb83DZEm1OsAf4i0pODz21PY/I+O
|
||||
VHzjB10oYgaInHZgMUdyb6F571UdiYSB6a/IlZ3ngj5touy3VIM=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
// PrivateKey2 is a second RSA key used only for tests. You can use it to see if signing a JWT with a different key
|
||||
// fails validation (as it should).
|
||||
var PrivateKey2 = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIG4gIBAAKCAYEAqGvlMTqPxJ844hNAneTzh9lPlYx0swai2RONOGLF0/0I9Ej5
|
||||
TIgVvGykcoH3e39VGAUFd8qbLKneX3nPMjhe1+0dmLPhEGffO2ZEkhMBM0x6bhYX
|
||||
XIsCXly5unN/Boosibvsd9isItsnC+4m3ELyREj1gTsCqIoZxFEq2iCPhfS7uPlQ
|
||||
z8G0q0FJohNOJEXYzH96Z4xuI3zudux5PPiHNsCzoUs/X0ogda14zaolvvZPYaqg
|
||||
g5zmZz6dHWnnKogsp0+Q1V3Nz1/GTCs6IDURSX+EPxst5qcin92Ft6TLOb0pu/dQ
|
||||
BW90AGspoelB54iElwbmib58KBzLC8U0FZIfcuN/vOfEnv7ON4RAS/R6wKRMdPEy
|
||||
Fm+Lr65QntaW2AVdxFM7EZfWLFOv741fMT3a1/l3Wou+nalxe7M+epFcn67XrkIi
|
||||
fLnvg/rOUESNHmfuFIa9CAJdekM1WxCFBq6/rAxmHdnbEX3SCl0h1SrzaF336JQc
|
||||
PMSNGiNjra5xO8CxAgMBAAECggGAfvBLXy5HO6fSJLrkAd2VG3fTfuDM+D3xMXGG
|
||||
B9CSUDOvswbpNyB+WXT9AP0p/V+8UA1A0MfY6vHhE87oNm68NTyXCQfSgx3253su
|
||||
BXbjebmTsTNfSjXPhDWZGomAXPp5lRoZoT6ihubsaBaIHY0rsgHXYB6M42CrCQcw
|
||||
KBVQd2M8ta7blKrntAfSKqEoTTiDraYLKM50GLVJukKDIkwjBUZ6XQAs9HIXQvqL
|
||||
SV+LcYGN1QvYTTpNgdV0b73pKGpXG8AvuwXrYFKTZeNMxPnbXd5NHLE6efuOHfeb
|
||||
gYoDFy7NLSJa7DdpJIYMf0yMZQVOwdcKXiK2st+e0mUS0WHNhGKQAVc3wd+gzgtS
|
||||
+s/hJk/ya/4CJwXahtbn5zhNDdbgMSt+m2LVRCIGd+JL14cd1bPySD9QL3EU7+9P
|
||||
nt4S9wvu2lqa6VSK2I9tsjIgm7I7T5SUI3m+DnrpTzlpDCOqFccsSIlY5I+BD9ES
|
||||
7bT57cRkyeWh5w43UQeSFhul5T0tAoHBAN7BjlT22hynPNPshNtJIj+YbAX9+MV9
|
||||
FIjyPa1Say/PSXf9SvRWaTDuRWnFy4B9c12p6zwtbFjewn6OBCope26mmjVtii6t
|
||||
4ABhA/v17nPUjLMQQZGIE0pHGKMpspmd3hqZcNomTtdTNy9X7NBCigJNeZR17TFm
|
||||
3F2qh9oNJVbAgO54PbmFiWk0vMr0x6PWA0p3Ns/qPdu7s7EFonyOHs7f3E9MCYEd
|
||||
3rp5IOJ5rzFR0acYbYhsOX4zRgMRrMYb5wKBwQDBjnlFzZVF56eK5iseEZrnay5p
|
||||
CsLqxDGKr8wHFHQ8G9hGLTGOsaPd3RvAD2A7rQSNNHj2S2gv8I8DBOXzFhifE31q
|
||||
Cy7Zh0HjAt6Tx5yL/lKAPMbDC3trUdITJugepR72t27UmLY0ZAX0SS8ZCRg+3dAS
|
||||
Vdp3zkfOhlg3w92eQSdnU+hmr44AJL1cU+CLN7pCZgkaXzuULfs/+tPVVyeOHZX7
|
||||
iA2fJ2ITRzO9XjclQ49itRJWqWcq22JqsgQ6a6cCgcBn9blxmcttd/eBiG7w0I71
|
||||
UzOHEGKb+KYuy69RRpfTtlA5ebMTmYh6V5l5peA11VaULgslCKX6S+xFmA4Fh1qd
|
||||
548sxDSrWGakhqKPYtWopVgM8ddIDlPCZK/w5jL+UpknnNj4VsyQ3btxkv1orMUw
|
||||
EexeBzNtzO2noUDJ2TzF4g3KPb/A57ubqAs8RUUvB2B9zml8W3wHIvDX+yM8Mi/a
|
||||
qMtvDrOY2NHsAUABsny67c6Ex3fHJYsnhNJ1+DfENZ0CgcBuewR983rhC/l2Lyst
|
||||
Xp8suOEk1B+uIY6luvKal/JA3SP16pX+/Sar3SmZ1yz24ytV7j2dWC2AL69x6bnX
|
||||
pyUmp9lOTlPPloTlLx4c/DM/NUuiJw7NBiDMgUeH5w1XcKjb6pg4gXJ/NRiw95UK
|
||||
lUZhm/rIfHjXKceS+twf+IznaAk10Y82Db7gFhiAOuBQlt6aR+OqSfGYAycGvgVs
|
||||
IPNTC1Aw4tfjoHc6ycmerciMXKPbk7+D9+4LaG4kuLfxIMECgcANm3mBWWJCFH3h
|
||||
s2PXArzk1G9RKEmfUpfhVkeMhtD2/TMG3NPvrGpmjmPx5rf1DUxOUMJyu+B1VdZg
|
||||
u0GOSkEiOfI3DxNs0GwzsL9/EYoelgGj7uc6IV9awhbzRPwro5nceGJspnWqXIVp
|
||||
rawN1NFkKr5MCxl5Q4veocU94ThOlFdYgreyVX6s40ZL1eF0RvAQ+e0oFT7SfCHu
|
||||
B3XwyYtAFsaO5r7oEc1Bv6oNSbE+FNJzRdjkWEIhdLVKlepil/w=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
func dexMockHandler(t *testing.T, url string) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -123,6 +169,20 @@ func oidcMockHandler(t *testing.T, url string) func(http.ResponseWriter, *http.R
|
||||
"claims_supported": ["sub", "aud", "exp"]
|
||||
}`, url))
|
||||
require.NoError(t, err)
|
||||
case "/keys":
|
||||
pubKey, err := jwt.ParseRSAPublicKeyFromPEM(Cert)
|
||||
require.NoError(t, err)
|
||||
jwks := jose.JSONWebKeySet{
|
||||
Keys: []jose.JSONWebKey{
|
||||
{
|
||||
Key: pubKey,
|
||||
},
|
||||
},
|
||||
}
|
||||
out, err := json.Marshal(jwks)
|
||||
require.NoError(t, err)
|
||||
_, err = io.WriteString(w, string(out))
|
||||
require.NoError(t, err)
|
||||
default:
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
@@ -137,4 +197,4 @@ func GetOIDCTestServer(t *testing.T) *httptest.Server {
|
||||
oidcMockHandler(t, ts.URL)(w, r)
|
||||
})
|
||||
return ts
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ type settingsSource interface {
|
||||
GetTrackingMethod() (string, error)
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc3986#section-3.2.1
|
||||
// https://github.com/shadow-maint/shadow/blob/master/libmisc/chkname.c#L36
|
||||
const usernameRegex = `[a-zA-Z0-9_\.][a-zA-Z0-9_\.-]{0,30}[a-zA-Z0-9_\.\$-]?`
|
||||
|
||||
var _ settingsSource = &settings.SettingsManager{}
|
||||
|
||||
type ArgoCDWebhookHandler struct {
|
||||
@@ -262,7 +266,8 @@ func getWebUrlRegex(webURL string) (*regexp.Regexp, error) {
|
||||
|
||||
regexEscapedHostname := regexp.QuoteMeta(urlObj.Hostname())
|
||||
regexEscapedPath := regexp.QuoteMeta(urlObj.Path[1:])
|
||||
regexpStr := fmt.Sprintf(`(?i)^(http://|https://|\w+@|ssh://(\w+@)?)%s(:[0-9]+|)[:/]%s(\.git)?$`, regexEscapedHostname, regexEscapedPath)
|
||||
regexpStr := fmt.Sprintf(`(?i)^(http://|https://|%s@|ssh://(%s@)?)%s(:[0-9]+|)[:/]%s(\.git)?$`,
|
||||
usernameRegex, usernameRegex, regexEscapedHostname, regexEscapedPath)
|
||||
repoRegexp, err := regexp.Compile(regexpStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compile regexp for repoURL '%s'", webURL)
|
||||
|
||||
@@ -427,6 +427,8 @@ func Test_getWebUrlRegex(t *testing.T) {
|
||||
{true, "https://example.com/org/repo", "ssh://git@example.com/org/repo", "git with protocol should match"},
|
||||
{true, "https://example.com/org/repo", "ssh://git@example.com:22/org/repo", "git with port number should should match"},
|
||||
{true, "https://example.com:443/org/repo", "ssh://git@example.com:22/org/repo", "https and ssh w/ different port numbers should match"},
|
||||
{true, "https://example.com/org/repo", "ssh://user-name@example.com/org/repo", "valid usernames with hyphens in repo should match"},
|
||||
{false, "https://example.com/org/repo", "ssh://-user-name@example.com/org/repo", "invalid usernames with hyphens in repo should not match"},
|
||||
{true, "https://example.com:443/org/repo", "GIT@EXAMPLE.COM:22:ORG/REPO", "matches aren't case-sensitive"},
|
||||
}
|
||||
for _, testCase := range tests {
|
||||
|
||||
Reference in New Issue
Block a user