Compare commits

...

63 Commits

Author SHA1 Message Date
argo-bot
91aefabc5b Bump version to 2.4.0 2022-06-10 17:13:43 +00:00
argo-bot
56b8e2f356 Bump version to 2.4.0 2022-06-10 17:13:30 +00:00
Michael Crenshaw
101477a638 docs: document OpenSSH upgrade (#9598) (#9615)
docs: document OpenSSH upgrade (#9598) (#9615)

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-09 15:59:59 -07:00
Alexander Matyushentsev
9bf7d1b95b fix: change repo-server command to expand 'ARGOCD_REDIS_SERVICE' env variable (#9628)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2022-06-09 15:59:54 -07:00
Daniel Helfand
da8e7b9697 fix: use serviceaccount name instead of struct (#9614) (#9617)
* fix: use serviceaccount name instead of struct

Signed-off-by: Daniel Helfand <helfand.4@gmail.com>

* fix: change name of param from sa to serviceAccount

Signed-off-by: Daniel Helfand <helfand.4@gmail.com>
2022-06-08 16:20:29 -04:00
Daniel Helfand
eb183dcde1 fix: create serviceaccount token for v1.24 clusters (#9546)
* fix: create serviceaccount token for v1.24 clusters

Signed-off-by: Daniel Helfand <helfand.4@gmail.com>

* change create to get in err

Signed-off-by: Daniel Helfand <helfand.4@gmail.com>
2022-06-08 14:01:25 -04:00
Michael Crenshaw
ccecc693c2 docs: document argocd cluster add behavior for 1.24 clusters (#9611)
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-08 13:54:38 -04:00
Yaakov Selkowitz
dc1d4b060d chore: Fix working directory for remote e2e test image (#9600)
Commit cc6c625401 changed a RUN mkdir
command into a WORKDIR, which also affected the entrypoint.  This
triggered an error in goreman which looks for Procfile (which is
installed here in the root directory) in the working directory.

Since COPY creates any missing directories in the destination path,
there is no need for a separate step to create it.  This change leaves
WORKDIR as the default (the root directory) as before.

Signed-off-by: Yaakov Selkowitz <yselkowi@redhat.com>
2022-06-07 17:31:33 +00:00
Michael Crenshaw
512233806c chore: fix long socket path breaking test on osx (#9391)
* chore: fix long socket path breaking test on osx

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* comment

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* simplify

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* simplify

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-06 17:48:42 -04:00
argo-bot
b84dd8bbfa Bump version to 2.4.0-rc5 2022-06-06 19:03:38 +00:00
argo-bot
e6f37d7245 Bump version to 2.4.0-rc5 2022-06-06 19:03:30 +00:00
Michael Crenshaw
57bac9ac75 chore: update changelog
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-06 14:59:46 -04:00
34FathomBelow
3034183791 chore: upgrade base image to ubuntu:20.04 (#9551)
Signed-off-by: douhunt <douhunt@protonmail.com>

Co-authored-by: douhunt <douhunt@protonmail.com>
Co-authored-by: Michael Crenshaw <michael@crenshaw.dev>
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-06 14:52:10 -04:00
34FathomBelow
26c87b3f16 chore upgrade base image for test containers Ubuntu:22.04 (#9563)
Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
2022-06-06 14:52:02 -04:00
34FathomBelow
bd8d26d444 chore: update Kex-Algorithms (#9561)
* chore: update Kex-Algorithms

Signed-off-by: douhunt <douhunt@protonmail.com>

* sorted kex-algorithms

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

Co-authored-by: douhunt <douhunt@protonmail.com>
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-06 14:51:54 -04:00
Michael Crenshaw
a6c58748cd fix: missing Helm params (#9565) (#9566)
* fix: missing Helm params

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* use absolute paths, fix tests

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* fix race in test

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-04 20:17:12 -04:00
Bjorn Stange
46af59e258 docs: fix cm typo (#9577)
Signed-off-by: Bjorn Stange <bjorn.stange@expel.io>
2022-06-03 13:21:08 -04:00
Alexander Matyushentsev
867660a709 chore: remove obsolete repo-server unit test (#9559)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2022-06-03 09:30:57 -04:00
margueritepd
b1addf5bf1 docs: document action RBAC action and application resource path (#8413)
Signed-off-by: Marguerite des Trois Maisons <marguerite+github@pagerduty.com>

Co-authored-by: Michael Crenshaw <michael@crenshaw.dev>
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-02 17:51:08 -04:00
Pierre Crégut
0a9d1607e2 feat: Add plugin call variables to sidecar plugin discovery (#9273) (#9319)
* fix: do not export repo-server environment to sidecar (#9393)

getPluginEnvs is both used for local plugins and sidecar plugins. For the later
do not include the environement variables of the repo-server in the supplied
variables.

Fixes: #9393
Signed-off-by: Pierre Crégut <pierre.cregut@orange.com>

* feat: Add plugin call variables to sidecar plugin discovery (#9273)

Gives access to variables declared in the call of the plugin in the application
manifest to the discover command run on the CMP server.

Variables are prefixed with ARGOCD_ENV_ to avoid security issues (plugin call
overiding important variables).

Fixes #9273

Signed-off-by: Pierre Crégut <pierre.cregut@orange.com>
2022-06-02 17:50:29 -04:00
Michael Crenshaw
09fc32e6cb docs: document plugin prefixed env vars (#9548)
* docs: document plugin prefixed env vars

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* restructure, clarify env vars from main container behavior

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* grammer

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* grammer

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* no link - the cert is bad

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* fixes

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-06-02 15:46:50 -04:00
argo-bot
1be9296e6c Bump version to 2.4.0-rc4 2022-06-01 22:49:24 +00:00
argo-bot
76fad02f4a Bump version to 2.4.0-rc4 2022-06-01 22:49:15 +00:00
Alexander Matyushentsev
2f8eb04b84 fix: web terminal due to query parameters name mismatch (#9560)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2022-06-01 14:39:32 -07:00
argo-bot
66aa0e6e01 Bump version to 2.4.0-rc3 2022-05-31 19:24:04 +00:00
argo-bot
955270eb0d Bump version to 2.4.0-rc3 2022-05-31 19:23:57 +00:00
Michael Crenshaw
225a0af9f7 docs: security warning for plugins (#9547)
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-05-31 11:49:36 -07:00
Leonardo Luz Almeida
5191cd077c chore: Improve application logs adding message context (#9435)
* chore: Improve application logs adding message context

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* fix bug returning error incorrectly

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* Fix unit-test and avoid api breaking change

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* Fix test

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* Fix e2e test

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* Fix e2e test

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* small fix

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* Address review comments

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>
2022-05-31 14:19:02 -04:00
Leonardo Luz Almeida
c6b928c830 fix: Ignore diff with schema (#9170) 2022-05-31 10:27:32 -07:00
Michael Crenshaw
4e73b3c7ee docs: document new repo-server ServiceAccount (#9484)
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-05-31 12:28:38 -04:00
Michael Crenshaw
708c9e79b9 fix: avoid k8s call before authorization for terminal endpoint (#9434)
* fix: avoid k8s API call before authorization in k8s endpoint

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* check for bad project

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* lint

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* more logging

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* handle 404, return 500 instead of 400 for other errors

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* use user input

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* refactor validation

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* fix tests

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* fixes, tests

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-05-31 12:18:25 -04:00
ls0f
da2c249814 receiveFile memory optimization: do not use bytes.buffer but write directly to file (#9415)
Signed-off-by: ls0f <lovedboy.tk@qq.com>
2022-05-31 12:17:39 -04:00
smcavallo
c27cf3f95e feat: support pod exec terminal logging (#9385)
* feat: support pod exec terminal logging
Signed-off-by: smcavallo <smcavallo@hotmail.com>

* enhanced validation and logging when resource not found
Signed-off-by: smcavallo <smcavallo@hotmail.com>

* fix lint
Signed-off-by: smcavallo <smcavallo@hotmail.com>

* log warning when pod or container not found
Signed-off-by: smcavallo <smcavallo@hotmail.com>

* go/log-injection fixes
Signed-off-by: smcavallo <smcavallo@hotmail.com>

* log levels and lowercase message
Signed-off-by: smcavallo <smcavallo@hotmail.com>
2022-05-31 12:17:01 -04:00
Daniel Helfand
dbd3ce3133 docs: add applicationset and notifications controllers to running locally instructions (#9517)
Signed-off-by: Daniel Helfand <helfand.4@gmail.com>

Co-authored-by: Michael Crenshaw <michael@crenshaw.dev>
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-05-31 12:11:53 -04:00
Pavel Savchenko
58062c45de docs: Update 2.3 notes - mention helm chart (#9512)
the helm chart values should be copied as-is into the `notifications` section of the argo-cd chart

Signed-off-by: Pavel Savchenko <asfaltboy@gmail.com>
2022-05-31 12:11:35 -04:00
reggie-k
3c61070411 docs: logs RBAC known UI issue section (#9479)
* updated changelog and upgrade instructions to contain know UI issue with logs rbac

Signed-off-by: reggie-k <reginakagan@gmail.com>

* updated changelog and upgrade instructions to contain know UI issue with logs rbac

Signed-off-by: reggie-k <reginakagan@gmail.com>
2022-05-31 12:11:09 -04:00
Michael Crenshaw
697fc77379 docs: more appset security docs (#9466)
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-05-31 12:10:40 -04:00
Michael Crenshaw
6655a22b0a docs: plugins need their own writeable tmp volume (#9389)
* docs: plugin needs temp dir

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* revert temp changes

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* fix version number

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* update upgrade instructions

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* simplify

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-05-31 12:07:56 -04:00
Michael Crenshaw
0d109279a8 docs: fix PR generators list (#9387)
* docs: fix PR generators list

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* grammar

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-05-31 12:07:32 -04:00
neosu
4c1e1e0ad6 Fixes: #9364 (#9367)
In the #8929, the project parameter had changed to projects.

5f5d7aa59b/server/application/application.proto (L23)
Signed-off-by: neosu <neo@neobaran.com>
2022-05-30 19:19:27 -07:00
Michael Crenshaw
078cfe130b fix: test race (#9469)
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-05-24 08:15:41 -07:00
Michael Crenshaw
cd098638f8 fix: lint (#9444)
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-05-24 08:15:27 -07:00
Dan Garfield
2826a9215d refactor: Update notification engine (#9386)
refactor: Update notification engine  (#9386)

Signed-off-by: todaywasawesome <dan@codefresh.io>
2022-05-23 23:50:45 -07:00
Alexander Matyushentsev
13bef3a831 fix: api server should dynamically enabled terminal handler (#9497)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2022-05-23 23:49:56 -07:00
Alexander Matyushentsev
c6f80377a8 fix: Undefined cluster in UI when app is referencing cluster by name (#9493)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2022-05-23 22:52:34 -07:00
Alexander Matyushentsev
a8a451a84b fix: make more proto fields optional (#9490)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2022-05-23 13:46:15 -07:00
argo-bot
cd5b2af358 Bump version to 2.4.0-rc2 2022-05-18 12:32:45 +00:00
argo-bot
f6f9fa2cd6 Bump version to 2.4.0-rc2 2022-05-18 12:32:41 +00:00
jannfis
c7749ca67e Merge pull request from GHSA-r642-gv9p-2wjj
Signed-off-by: jannfis <jann@mistrust.net>

Co-authored-by: Michael Crenshaw <michael@crenshaw.dev>

Co-authored-by: Michael Crenshaw <michael@crenshaw.dev>
2022-05-18 13:16:22 +02:00
Michael Crenshaw
3399a81bed Merge pull request from GHSA-6gcg-hp2x-q54h
* fix: do not allow symlinks from directory-type applications

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* chore: use t.TempDir for simpler tests

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* address comments

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-05-18 13:13:41 +02:00
jannfis
80e5c55ca0 Merge pull request from GHSA-xmg8-99r8-jc2j
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

Co-authored-by: Michael Crenshaw <michael@crenshaw.dev>
2022-05-18 13:06:31 +02:00
Saumeya Katyal
c4182aedc7 fix: update filter combo-box icon color (#9416)
Signed-off-by: saumeya <saumeyakatyal@gmail.com>
2022-05-16 14:26:56 +00:00
Saumeya Katyal
e4404372af fix: favorite icon and overlapping app title (#9130)
Signed-off-by: saumeya <saumeyakatyal@gmail.com>
2022-05-16 14:26:35 +00:00
Leonardo Luz Almeida
12140f8152 chore: Improve otel grpc traces adding span correlation (#9371)
* chore: Improve otel grpc traces adding span correlation

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* Create a tracer for argocd controller

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* Update controller command doc

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* Instrument cmp-client

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* Add otlp config as part of configmaps

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* update manifests

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>
2022-05-13 15:13:43 -04:00
Chetan Banavikalmutt
6cfd394445 fix: ListResourceActions() returns duplicate actions (#9360)
Signed-off-by: Chetan Banavikalmutt <chetanrns1997@gmail.com>
2022-05-13 15:09:33 -04:00
Michael Crenshaw
1998b016c0 docs: clarify Role/ClusterRole uses for exec feature (#9354)
* docs: clarify Role/ClusterRole uses for exec feature

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* fix missed `get`s

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-05-12 11:14:10 -04:00
Michael Crenshaw
74bc1731f9 feat(manifests): Add service account for repo server (#9301) (#9355)
Signed-off-by: Hao Xin <haoxinst@gmail.com>
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

Co-authored-by: Hao Xin <haoxinst@qq.com>
2022-05-11 13:28:13 -04:00
Michael Crenshaw
bb28b3c697 docs: logs RBAC upgrate notes (#9345) (#9356)
Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-05-10 20:38:25 -04:00
Regina Scott
0ee9993369 fix: grouped node titles no longer wrap (#9340)
Signed-off-by: Regina Scott <rescott@redhat.com>
2022-05-09 19:34:14 -04:00
argo-bot
d8f845a126 Bump version to 2.4.0-rc1 2022-05-06 23:10:53 +00:00
argo-bot
5901b46785 Bump version to 2.4.0-rc1 2022-05-06 23:10:45 +00:00
David J. M. Karlsen
88542a616f feat: Add cli support for additional linux based architectures, s390x + ppc64le (#8991)
feat: Add cli support for additional linux based architectures, s390x + ppc64le (#8991)

Signed-off-by: David J. M. Karlsen <david@davidkarlsen.com>

* add more architectures for linux

Signed-off-by: David J. M. Karlsen <david@davidkarlsen.com>

* drop aix arch as it won't compile

Signed-off-by: David J. M. Karlsen <david@davidkarlsen.com>

Co-authored-by: Michael Crenshaw <michael@crenshaw.dev>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2022-05-06 16:10:03 -07:00
Alexander Matyushentsev
cbe4f1b92e chore: add linux/s390x,linux/ppc64le platforms to release workflow (#9324)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2022-05-06 16:09:59 -07:00
112 changed files with 14447 additions and 870 deletions

View File

@@ -202,7 +202,7 @@ jobs:
set -ue
git clean -fd
mkdir -p dist/
docker buildx build --platform linux/amd64,linux/arm64 --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 --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

View File

@@ -2,28 +2,32 @@
## v2.4.0 (Unreleased)
### Web Terminal In Argo CD UI
### Web Terminal In Argo CD UI
Feature enables engineers to start a shell in the running application container without leaving the web interface. Just find the required Kubernetes
Pod using the Application Details page, click on it and select the Terminal tab. The shell starts automatically and enables you to execute the required
commands, and helps to troubleshoot the application state.
### Access Control For Pod Logs & Web Terminal
### Access Control For Pod Logs & Web Terminal
Argo CD is used to manage the critical infrastructure of multiple organizations, which makes security the top priority of the project. We've listened to
your feedback and introduced additional access control settings that control access to Kubernetes Pod logs and the new Web Terminal feature.
#### Known UI Issue for Pod Logs Access
Currently, upon pressing the "LOGS" tab in pod view by users who don't have an explicit allow get logs policy, the red "unable to load data: Internal error" is received in the bottom of the screen, and "Failed to load data, please try again" is displayed.
### OpenTelemetry Tracing Integration
The new feature allows emitting richer telemetry data that might make identifying performance bottlenecks easier. The new feature is available for argocd-server
and argocd-repo-server components and can be enabled using the --otlp-address flag.
### Power PC and IBM Z Support
### Power PC and IBM Z Support
The list of supported architectures has been expanded, and now includes IBM Z (s390x) and PowerPC (ppc64le). Starting with the v2.4 release the official quay.io
repository is going to have images for amd64, arm64, ppc64le, and s390x architectures.
### Other Notable Changes
### Other Notable Changes
Overall v2.4 release includes more than 300 hundred commits from nearly 90 contributors. Here is a short sample of the contributions:

View File

@@ -1,4 +1,4 @@
ARG BASE_IMAGE=docker.io/library/ubuntu:21.10
ARG BASE_IMAGE=docker.io/library/ubuntu:22.04
####################################################################################################
# Builder image
# Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image
@@ -127,4 +127,4 @@ RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-server && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-applicationset-controller && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-k8s-auth
USER 999
USER 999

View File

@@ -234,6 +234,8 @@ release-cli: clean-debug build-ui
make BIN_NAME=argocd-darwin-arm64 GOOS=darwin GOARCH=arm64 argocd-all
make BIN_NAME=argocd-linux-amd64 GOOS=linux argocd-all
make BIN_NAME=argocd-linux-arm64 GOOS=linux GOARCH=arm64 argocd-all
make BIN_NAME=argocd-linux-ppc64le GOOS=linux GOARCH=ppc64le argocd-all
make BIN_NAME=argocd-linux-s390x GOOS=linux GOARCH=s390x argocd-all
make BIN_NAME=argocd-windows-amd64.exe GOOS=windows argocd-all
.PHONY: test-tools-image
@@ -566,4 +568,4 @@ applicationset-controller:
.PHONY: checksums
checksums:
for f in ./dist/$(BIN_NAME)-*; do openssl dgst -sha256 "$$f" | awk ' { print $$2 }' > "$$f".sha256 ; done
for f in ./dist/$(BIN_NAME)-*; do openssl dgst -sha256 "$$f" | awk ' { print $$2 }' > "$$f".sha256 ; done

View File

@@ -1,12 +1,12 @@
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_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} "
controller: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --otlp-address=${ARGOCD_OTLP_ADDRESS}"
api-server: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --otlp-address=${ARGOCD_OTLP_ADDRESS}"
dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v2/cmd gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:v2.30.2 dex serve /dex.yaml"
redis: bash -c "if [ \"$ARGOCD_REDIS_LOCAL\" == 'true' ]; then redis-server --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; else docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} redis:7.0.0-alpine --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; fi"
repo-server: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-repo-server ARGOCD_GPG_ENABLED=${ARGOCD_GPG_ENABLED:-false} $COMMAND --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"
cmp-server: [ "$ARGOCD_E2E_TEST" == 'true' ] && exit 0 || [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_BINARY_NAME=argocd-cmp-server ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} $COMMAND --config-dir-path ./test/cmp --loglevel debug"
repo-server: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-repo-server ARGOCD_GPG_ENABLED=${ARGOCD_GPG_ENABLED:-false} $COMMAND --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --otlp-address=${ARGOCD_OTLP_ADDRESS}"
cmp-server: [ "$ARGOCD_E2E_TEST" == 'true' ] && exit 0 || [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_BINARY_NAME=argocd-cmp-server ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} $COMMAND --config-dir-path ./test/cmp --loglevel debug --otlp-address=${ARGOCD_OTLP_ADDRESS}"
ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start'
git-server: test/fixture/testrepos/start-git.sh
helm-registry: test/fixture/testrepos/start-helm-registry.sh
dev-mounter: [[ "$ARGOCD_E2E_TEST" != "true" ]] && go run hack/dev-mounter/main.go --configmap argocd-ssh-known-hosts-cm=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} --configmap argocd-tls-certs-cm=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} --configmap argocd-gpg-keys-cm=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source}
applicationset-controller: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_ASK_PASS_SOCK=/tmp/applicationset-ask-pass.sock ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-applicationset-controller $COMMAND --loglevel debug --metrics-addr localhost:12345 --probe-addr localhost:12346 --argocd-repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
notification: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_BINARY_NAME=argocd-notifications $COMMAND --loglevel debug"
notification: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_BINARY_NAME=argocd-notifications $COMMAND --loglevel debug"

View File

@@ -166,7 +166,7 @@ func (g *ClusterGenerator) getSecretsByClusterName(appSetGenerator *argoappsetv1
}
// santize the name in accordance with the below rules
// sanitize the name in accordance with the below rules
// 1. contain no more than 253 characters
// 2. contain only lowercase alphanumeric characters, '-' or '.'
// 3. start and end with an alphanumeric character

View File

@@ -1,7 +1,7 @@
# Built-in policy which defines two roles: role:readonly and role:admin,
# and additionally assigns the admin user to the role:admin role.
# There are two policy formats:
# 1. Applications (which belong to a project):
# 1. Applications, logs, and exec (which belong to a project):
# p, <user/group>, <resource>, <action>, <project>/<object>
# 2. All other resources:
# p, <user/group>, <resource>, <action>, <object>
1 # Built-in policy which defines two roles: role:readonly and role:admin,
2 # and additionally assigns the admin user to the role:admin role.
3 # There are two policy formats:
4 # 1. Applications (which belong to a project): # 1. Applications, logs, and exec (which belong to a project):
5 # p, <user/group>, <resource>, <action>, <project>/<object>
6 # 2. All other resources:
7 # p, <user/group>, <resource>, <action>, <object>

View File

@@ -28,6 +28,7 @@ import (
kubeutil "github.com/argoproj/argo-cd/v2/util/kube"
"github.com/argoproj/argo-cd/v2/util/settings"
"github.com/argoproj/argo-cd/v2/util/tls"
"github.com/argoproj/argo-cd/v2/util/trace"
)
const (
@@ -58,6 +59,7 @@ func NewCommand() *cobra.Command {
redisClient *redis.Client
repoServerPlaintext bool
repoServerStrictTLS bool
otlpAddress string
)
var command = cobra.Command{
Use: cliName,
@@ -149,6 +151,14 @@ func NewCommand() *cobra.Command {
stats.StartStatsTicker(10 * time.Minute)
stats.RegisterHeapDumper("memprofile")
if otlpAddress != "" {
closeTracer, err := trace.InitTracer(ctx, "argocd-controller", otlpAddress)
if err != nil {
log.Fatalf("failed to initialize tracing: %v", err)
}
defer closeTracer()
}
go appController.Run(ctx, statusProcessors, operationProcessors)
// Wait forever
@@ -173,6 +183,7 @@ func NewCommand() *cobra.Command {
command.Flags().BoolVar(&repoServerPlaintext, "repo-server-plaintext", env.ParseBoolFromEnv("ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT", false), "Disable TLS on connections to repo server")
command.Flags().BoolVar(&repoServerStrictTLS, "repo-server-strict-tls", env.ParseBoolFromEnv("ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_STRICT_TLS", false), "Whether to use strict validation of the TLS cert presented by the repo server")
command.Flags().StringSliceVar(&metricsAplicationLabels, "metrics-application-labels", []string{}, "List of Application labels that will be added to the argocd_application_labels metric")
command.Flags().StringVar(&otlpAddress, "otlp-address", env.StringFromEnv("ARGOCD_APPLICATION_CONTROLLER_OTLP_ADDRESS", ""), "OpenTelemetry collector address to send traces to")
cacheSrc = appstatecache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
redisClient = client
})

View File

@@ -88,6 +88,7 @@ func TestGetReconcileResults_Refresh(t *testing.T) {
kubeClientset := kubefake.NewSimpleClientset(deployment, &cm)
clusterCache := clustermocks.ClusterCache{}
clusterCache.On("IsNamespaced", mock.Anything).Return(true, nil)
clusterCache.On("GetGVKParser", mock.Anything).Return(nil)
repoServerClient := mocks.RepoServerServiceClient{}
repoServerClient.On("GenerateManifest", mock.Anything, mock.Anything).Return(&argocdclient.ManifestResponse{
Manifests: []string{test.DeploymentManifest},

View File

@@ -609,7 +609,7 @@ func GenerateToken(clusterOpts cmdutil.ClusterOptions, conf *rest.Config) (strin
clientset, err := kubernetes.NewForConfig(conf)
errors.CheckError(err)
bearerToken, err := clusterauth.GetServiceAccountBearerToken(clientset, clusterOpts.SystemNamespace, clusterOpts.ServiceAccount)
bearerToken, err := clusterauth.GetServiceAccountBearerToken(clientset, clusterOpts.SystemNamespace, clusterOpts.ServiceAccount, common.BearerTokenTimeout)
if err != nil {
return "", err
}

View File

@@ -101,7 +101,7 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
clientset, err := kubernetes.NewForConfig(conf)
errors.CheckError(err)
if clusterOpts.ServiceAccount != "" {
managerBearerToken, err = clusterauth.GetServiceAccountBearerToken(clientset, clusterOpts.SystemNamespace, clusterOpts.ServiceAccount)
managerBearerToken, err = clusterauth.GetServiceAccountBearerToken(clientset, clusterOpts.SystemNamespace, clusterOpts.ServiceAccount, common.BearerTokenTimeout)
} else {
isTerminal := isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
@@ -115,7 +115,7 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
os.Exit(1)
}
}
managerBearerToken, err = clusterauth.InstallClusterManagerRBAC(clientset, clusterOpts.SystemNamespace, clusterOpts.Namespaces)
managerBearerToken, err = clusterauth.InstallClusterManagerRBAC(clientset, clusterOpts.SystemNamespace, clusterOpts.Namespaces, common.BearerTokenTimeout)
}
errors.CheckError(err)
}

View File

@@ -7,6 +7,7 @@ import (
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
@@ -46,6 +47,8 @@ func NewConnection(address string) (*grpc.ClientConn, error) {
grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor(retryOpts...)),
grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(unaryInterceptors...)),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxGRPCMessageSize), grpc.MaxCallSendMsgSize(MaxGRPCMessageSize)),
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),
grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()),
}
dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))

View File

@@ -233,12 +233,12 @@ func (s *Service) MatchRepository(stream apiclient.ConfigManagementPluginService
}
}()
_, err = cmp.ReceiveRepoStream(bufferedCtx, stream, workDir)
metadata, err := cmp.ReceiveRepoStream(bufferedCtx, stream, workDir)
if err != nil {
return fmt.Errorf("match repository error receiving stream: %s", err)
}
isSupported, err := s.matchRepository(bufferedCtx, workDir)
isSupported, err := s.matchRepository(bufferedCtx, workDir, metadata.GetEnv())
if err != nil {
return fmt.Errorf("match repository error: %s", err)
}
@@ -251,7 +251,7 @@ func (s *Service) MatchRepository(stream apiclient.ConfigManagementPluginService
return nil
}
func (s *Service) matchRepository(ctx context.Context, workdir string) (bool, error) {
func (s *Service) matchRepository(ctx context.Context, workdir string, envEntries []*apiclient.EnvEntry) (bool, error) {
config := s.initConstants.PluginConfig
if config.Spec.Discover.FileName != "" {
log.Debugf("config.Spec.Discover.FileName is provided")
@@ -284,7 +284,9 @@ func (s *Service) matchRepository(ctx context.Context, workdir string) (bool, er
}
log.Debugf("Going to try runCommand.")
find, err := runCommand(ctx, config.Spec.Discover.Find.Command, workdir, os.Environ())
env := append(os.Environ(), environ(envEntries)...)
find, err := runCommand(ctx, config.Spec.Discover.Find.Command, workdir, env)
if err != nil {
return false, fmt.Errorf("error running find command: %s", err)
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/v2/cmpserver/apiclient"
"github.com/argoproj/argo-cd/v2/test"
)
@@ -62,6 +63,7 @@ func TestMatchRepository(t *testing.T) {
type fixture struct {
service *Service
path string
env []*apiclient.EnvEntry
}
setup := func(t *testing.T, opts ...pluginOpt) *fixture {
t.Helper()
@@ -71,6 +73,7 @@ func TestMatchRepository(t *testing.T) {
return &fixture{
service: s,
path: path,
env: []*apiclient.EnvEntry{{Name: "ENV_VAR", Value: "1"}},
}
}
t.Run("will match plugin by filename", func(t *testing.T) {
@@ -81,7 +84,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
match, err := f.service.matchRepository(context.Background(), f.path)
match, err := f.service.matchRepository(context.Background(), f.path, f.env)
// then
assert.NoError(t, err)
@@ -95,7 +98,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
match, err := f.service.matchRepository(context.Background(), f.path)
match, err := f.service.matchRepository(context.Background(), f.path, f.env)
// then
assert.NoError(t, err)
@@ -111,7 +114,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
match, err := f.service.matchRepository(context.Background(), f.path)
match, err := f.service.matchRepository(context.Background(), f.path, f.env)
// then
assert.NoError(t, err)
@@ -127,7 +130,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
match, err := f.service.matchRepository(context.Background(), f.path)
match, err := f.service.matchRepository(context.Background(), f.path, f.env)
// then
assert.NoError(t, err)
@@ -145,7 +148,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
match, err := f.service.matchRepository(context.Background(), f.path)
match, err := f.service.matchRepository(context.Background(), f.path, f.env)
// then
assert.NoError(t, err)
@@ -163,7 +166,43 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
match, err := f.service.matchRepository(context.Background(), f.path)
match, err := f.service.matchRepository(context.Background(), f.path, f.env)
// then
assert.NoError(t, err)
assert.False(t, match)
})
t.Run("will match plugin because env var defined", func(t *testing.T) {
// given
d := Discover{
Find: Find{
Command: Command{
Command: []string{"sh", "-c", "echo -n $ENV_VAR"},
},
},
}
f := setup(t, withDiscover(d))
// when
match, err := f.service.matchRepository(context.Background(), f.path, f.env)
// then
assert.NoError(t, err)
assert.True(t, match)
})
t.Run("will not match plugin because no env var defined", func(t *testing.T) {
// given
d := Discover{
Find: Find{
Command: Command{
Command: []string{"sh", "-c", "echo -n $ENV_NO_VAR"},
},
},
}
f := setup(t, withDiscover(d))
// when
match, err := f.service.matchRepository(context.Background(), f.path, f.env)
// then
assert.NoError(t, err)
@@ -181,7 +220,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
match, err := f.service.matchRepository(context.Background(), f.path)
match, err := f.service.matchRepository(context.Background(), f.path, f.env)
// then
assert.Error(t, err)

View File

@@ -229,6 +229,12 @@ const (
CacheVersion = "1.8.3"
)
// Constants used by util/clusterauth package
const (
ClusterAuthRequestTimeout = 10 * time.Second
BearerTokenTimeout = 30 * time.Second
)
const (
DefaultGitRetryMaxDuration time.Duration = time.Second * 5 // 5s
DefaultGitRetryDuration time.Duration = time.Millisecond * 250 // 0.25s

View File

@@ -605,11 +605,16 @@ func (ctrl *ApplicationController) hideSecretData(app *appv1.Application, compar
return nil, fmt.Errorf("error getting tracking method: %s", err)
}
clusterCache, err := ctrl.stateCache.GetClusterCache(app.Spec.Destination.Server)
if err != nil {
return nil, fmt.Errorf("error getting cluster cache: %s", err)
}
diffConfig, err := argodiff.NewDiffConfigBuilder().
WithDiffSettings(app.Spec.IgnoreDifferences, resourceOverrides, compareOptions.IgnoreAggregatedRoles).
WithTracking(appLabelKey, trackingMethod).
WithNoCache().
WithLogger(logutils.NewLogrusLogger(logutils.NewWithCurrentConfig())).
WithGVKParser(clusterCache.GetGVKParser()).
Build()
if err != nil {
return nil, fmt.Errorf("appcontroller error building diff config: %s", err)

View File

@@ -121,6 +121,7 @@ func newFakeController(data *fakeData) *ApplicationController {
clusterCacheMock := mocks.ClusterCache{}
clusterCacheMock.On("IsNamespaced", mock.Anything).Return(true, nil)
clusterCacheMock.On("GetOpenAPISchema").Return(nil, nil)
clusterCacheMock.On("GetGVKParser").Return(nil)
mockStateCache := mockstatecache.LiveStateCache{}
ctrl.appStateManager.(*appStateManager).liveStateCache = &mockStateCache

View File

@@ -461,7 +461,14 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
} else {
diffConfigBuilder.WithCache(m.cache, app.GetName())
}
// it necessary to ignore the error at this point to avoid creating duplicated
gvkParser, err := m.getGVKParser(app.Spec.Destination.Server)
if err != nil {
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionUnknownError, Message: err.Error(), LastTransitionTime: &now})
}
diffConfigBuilder.WithGVKParser(gvkParser)
// it is necessary to ignore the error at this point to avoid creating duplicated
// application conditions as argo.StateDiffs will validate this diffConfig again.
diffConfig, _ := diffConfigBuilder.Build()

View File

@@ -17,6 +17,7 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/managedfields"
"k8s.io/kubectl/pkg/util/openapi"
cdcommon "github.com/argoproj/argo-cd/v2/common"
@@ -46,6 +47,14 @@ func (m *appStateManager) getOpenAPISchema(server string) (openapi.Resources, er
return cluster.GetOpenAPISchema(), nil
}
func (m *appStateManager) getGVKParser(server string) (*managedfields.GvkParser, error) {
cluster, err := m.liveStateCache.GetClusterCache(server)
if err != nil {
return nil, err
}
return cluster.GetGVKParser(), nil
}
func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha1.OperationState) {
// Sync requests might be requested with ambiguous revisions (e.g. master, HEAD, v1.2.3).
// This can change meaning when resuming operations (e.g a hook sync). After calculating a

View File

@@ -27,6 +27,8 @@ kubectl -n argocd scale deployment/argocd-dex-server --replicas 0
kubectl -n argocd scale deployment/argocd-repo-server --replicas 0
kubectl -n argocd scale deployment/argocd-server --replicas 0
kubectl -n argocd scale deployment/argocd-redis --replicas 0
kubectl -n argocd scale deployment/argocd-applicationset-controller --replicas 0
kubectl -n argocd scale deployment/argocd-notifications-controller --replicas 0
```
### Start local services

View File

@@ -45,7 +45,7 @@ spec:
metadata:
name: '{{name}}-guestbook' # 'name' field of the Secret
spec:
project: "default"
project: "my-project"
source:
repoURL: https://github.com/argoproj/argocd-example-apps/
targetRevision: HEAD
@@ -144,7 +144,7 @@ spec:
metadata:
name: '{{name}}-guestbook'
spec:
project: "default"
project: "my-project"
source:
repoURL: https://github.com/argoproj/argocd-example-apps/
# The cluster values field for each generator will be substituted here:

View File

@@ -2,6 +2,12 @@
The Git generator contains two subtypes: the Git directory generator, and Git file generator.
!!! warning
Git generators are often used to make it easier for (non-admin) developers to create Applications.
If the `project` field in your ApplicationSet is templated, developers may be able to create Applications under Projects with excessive permissions.
For ApplicationSets with a templated `project` field, [the source of truth _must_ be controlled by admins](./Security.md#templated-project-field)
- in the case of git generators, PRs must require admin approval.
## Git Generator: Directories
The Git directory generator, one of two subtypes of the Git generator, generates parameters using the directory structure of a specified Git repository.
@@ -41,7 +47,7 @@ spec:
metadata:
name: '{{path[0]}}'
spec:
project: default
project: "my-project"
source:
repoURL: https://github.com/argoproj/argo-cd.git
targetRevision: HEAD
@@ -88,7 +94,7 @@ spec:
metadata:
name: '{{path.basename}}'
spec:
project: default
project: "my-project"
source:
repoURL: https://github.com/argoproj/argo-cd.git
targetRevision: HEAD

View File

@@ -20,7 +20,7 @@ spec:
metadata:
name: '{{cluster}}-guestbook'
spec:
project: default
project: "my-project"
source:
repoURL: https://github.com/argoproj/argo-cd.git
targetRevision: HEAD

View File

@@ -1,7 +1,6 @@
# Pull Request Generator
The Pull Request generator uses the API of an SCMaaS provider (eg GitHub/GitLab) to automatically discover open pull requests within an repository. This fits well with the style of building a test environment when you create a pull request.
The Pull Request generator uses the API of an SCMaaS provider (GitHub, Gitea, or Bitbucket Server) to automatically discover open pull requests within a repository. This fits well with the style of building a test environment when you create a pull request.
```yaml
apiVersion: argoproj.io/v1alpha1
@@ -16,9 +15,15 @@ spec:
# ...
```
!!! note
Know the security implications of PR generators in ApplicationSets.
[Only admins may create ApplicationSets](./Security.md#only-admins-may-createupdatedelete-applicationsets) to avoid
leaking Secrets, and [only admins may create PRs](./Security.md#templated-project-field) if the `project` field of
an ApplicationSet with a PR generator is templated, to avoid granting management of out-of-bounds resources.
## GitHub
Specify the repository from which to fetch the Github Pull requests.
Specify the repository from which to fetch the GitHub Pull requests.
```yaml
apiVersion: argoproj.io/v1alpha1
@@ -48,7 +53,7 @@ spec:
```
* `owner`: Required name of the GitHub organization or user.
* `repo`: Required name of the Github repository.
* `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)
@@ -182,7 +187,7 @@ spec:
parameters:
- name: "image.tag"
value: "pull-{{head_sha}}"
project: default
project: "my-project"
destination:
server: https://kubernetes.default.svc
namespace: default
@@ -213,7 +218,7 @@ spec:
app.kubernetes.io/instance: {{branch}}-{{number}}
images:
- ghcr.io/myorg/myrepo:{{head_sha}}
project: default
project: "my-project"
destination:
server: https://kubernetes.default.svc
namespace: default
@@ -230,6 +235,7 @@ When using a Pull Request generator, the ApplicationSet controller polls every `
The configuration is almost the same as the one described [in the Git generator](Generators-Git.md), but there is one difference: if you want to use the Pull Request Generator as well, additionally configure the following settings.
In section 1, _"Create the webhook in the Git provider"_, add an event so that a webhook request will be sent when a pull request is created, closed, or label changed.
Select `Let me select individual events` and enable the checkbox for `Pull requests`.
![Add Webhook](../../assets/applicationset/webhook-config-pull-request.png "Add Webhook Pull Request")

View File

@@ -19,6 +19,12 @@ spec:
* `cloneProtocol`: Which protocol to use for the SCM URL. Default is provider-specific but ssh if possible. Not all providers necessarily support all protocols, see provider documentation below for available options.
!!! note
Know the security implications of using SCM generators. [Only admins may create ApplicationSets](./Security.md#only-admins-may-createupdatedelete-applicationsets)
to avoid leaking Secrets, and [only admins may create repos/branches](./Security.md#templated-project-field) if the
`project` field of an ApplicationSet with an SCM generator is templated, to avoid granting management of
out-of-bounds resources.
## GitHub
The GitHub mode uses the GitHub API to scan and organization in either github.com or GitHub Enterprise.

View File

@@ -22,9 +22,9 @@ Follow the [Argo CD Getting Started](../../getting_started.md) instructions for
### B) Install ApplicationSet into an existing Argo CD install (pre-Argo CD v2.3)
**Note**: These instruction only apply to versions of Argo CD before v2.3.0.
**Note**: These instructions only apply to versions of Argo CD before v2.3.0.
The ApplicationSet controller *must* be installed into the same namespace as the Argo CD it is targetting.
The ApplicationSet controller *must* be installed into the same namespace as the Argo CD it is targeting.
Presuming that Argo CD is installed into the `argocd` namespace, run the following command:

View File

@@ -0,0 +1,38 @@
# ApplicationSet Security
ApplicationSet is a powerful tool, and it is crucial to understand its security implications before using it.
## Only admins may create/update/delete ApplicationSets
ApplicationSets can create Applications under arbitrary [Projects](../../user-guide/projects.md). Argo CD setups often
include Projects (such as the `default`) with high levels of permissions, often including the ability to manage the
resources of Argo CD itself (like the RBAC ConfigMap).
ApplicationSets can also quickly create an arbitrary number of Applications and just as quickly delete them.
Finally, ApplicationSets can reveal privileged information. For example, the [git generator](./Generators-Git.md) can
read Secrets in the Argo CD namespace and send them to arbitrary URLs as auth headers. (This functionality is intended
for authorizing requests to SCM providers like GitHub, but it could be abused by a malicious user.)
For these reasons, **only admins** may be given permission (via Kubernetes RBAC or any other mechanism) to create,
update, or delete ApplicationSets.
## Admins must apply appropriate controls for ApplicationSets' sources of truth
Even if non-admins can't create ApplicationSet resources, they may be able to affect the behavior of ApplicationSets.
For example, if an ApplicationSet uses a [git generator](./Generators-Git.md), a malicious user with push access to the
source git repository could generate an excessively high number of Applications, putting strain on the ApplicationSet
and Application controllers. They could also cause the SCM provider's rate limiting to kick in, degrading ApplicationSet
service.
### Templated `project` field
It's important to pay special attention to ApplicationSets where the `project` field is templated. A malicious user with
write access to the generator's source of truth (for example, someone with push access to the git repo for a git
generator) could create Applications under Projects with insufficient restrictions. A malicious user with the ability to
create an Application under an unrestricted Project (like the `default` Project) could take control of Argo CD itself
by, for example, modifying its RBAC ConfigMap.
If the `project` field is not hard-coded in an ApplicationSet's template, then admins _must_ control all sources of
truth for the ApplicationSet's generators.

View File

@@ -15,6 +15,9 @@ The ApplicationSet controller, supplements Argo CD by adding additional features
- Improved support for monorepos: in the context of Argo CD, a monorepo is multiple Argo CD Application resources defined within a single Git repository
- Within multitenant clusters, improves the ability of individual cluster tenants to deploy applications using Argo CD (without needing to involve privileged cluster administrators in enabling the destination clusters/namespaces)
!!! note
Be aware of the [security implications](./Security.md) of ApplicationSets before using them.
## The ApplicationSet resource
This example defines a new `guestbook` resource of kind `ApplicationSet`:
@@ -37,6 +40,7 @@ spec:
metadata:
name: '{{cluster}}-guestbook'
spec:
project: my-project
source:
repoURL: https://github.com/infra-team/cluster-deployments.git
targetRevision: HEAD

View File

@@ -17,6 +17,9 @@ data:
# Redis database
redis.db:
# Open-Telemetry collector address: (e.g. "otel-collector:4317")
otlp.address:
## Controller Properties
# Repo server RPC call timeout seconds.
controller.repo.server.timeout.seconds: "60"
@@ -103,4 +106,4 @@ data:
reposerver.repo.cache.expiration: "24h0m0s"
# Cache expiration default (default 24h0m0s)
reposerver.default.cache.expiration: "24h0m0s"

View File

@@ -0,0 +1,66 @@
# Pagerduty
## Parameters
The Pagerduty notification service is used to create pagerduty incidents and requires specifying the following settings:
* `pagerdutyToken` - the pagerduty auth token
* `from` - email address of a valid user associated with the account making the request.
* `serviceID` - The ID of the resource.
## Example
The following snippet contains sample Pagerduty service configuration:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: <secret-name>
stringData:
pagerdutyToken: <pd-api-token>
```
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: <config-map-name>
data:
service.pagerduty: |
token: $pagerdutyToken
from: <emailid>
```
## Template
Notification templates support specifying subject for pagerduty notifications:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: <config-map-name>
data:
template.rollout-aborted: |
message: Rollout {{.rollout.metadata.name}} is aborted.
pagerduty:
title: "Rollout {{.rollout.metadata.name}}"
urgency: "high"
body: "Rollout {{.rollout.metadata.name}} aborted "
priorityID: "<priorityID of incident>"
```
NOTE: A Priority is a label representing the importance and impact of an incident. This is only available on Standard and Enterprise plans of pagerduty.
## Annotation
Annotation sample for pagerduty notifications:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
annotations:
notifications.argoproj.io/subscribe.on-rollout-aborted.pagerduty: "<serviceID for Pagerduty>"
```

View File

@@ -54,6 +54,26 @@ The Slack notification service configuration includes following settings:
annotations:
notifications.argoproj.io/subscribe.on-sync-succeeded.slack: my_channel
1. Annotation with more than one trigger multiple of destinations and recipients
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
annotations:
notifications.argoproj.io/subscriptions: |
- trigger: [on-scaling-replica-set, on-rollout-updated, on-rollout-step-completed]
destinations:
- service: slack
recipients: [my-channel-1, my-channel-2]
- service: email
recipients: [recipient-1, recipient-2, recipient-3 ]
- trigger: [on-rollout-aborted, on-analysis-run-failed, on-analysis-run-error]
destinations:
- service: slack
recipients: [my-channel-21, my-channel-22]
```
## Templates
Notification templates can be customized to leverage slack message blocks and attachments

View File

@@ -18,11 +18,11 @@ These default built-in role definitions can be seen in [builtin-policy.csv](http
Breaking down the permissions definition differs slightly between applications and every other resource type in Argo CD.
* All resources *except* applications permissions (see next bullet):
* All resources *except* application-specific permissions (see next bullet):
`p, <role/user/group>, <resource>, <action>, <object>`
* Applications (which belong to an AppProject):
* Applications, logs, and exec (which belong to an AppProject):
`p, <role/user/group>, <resource>, <action>, <appproject>/<object>`
@@ -30,7 +30,29 @@ Breaking down the permissions definition differs slightly between applications a
Resources: `clusters`, `projects`, `applications`, `repositories`, `certificates`, `accounts`, `gpgkeys`, `logs`, `exec`
Actions: `get`, `create`, `update`, `delete`, `sync`, `override`, `action`
Actions: `get`, `create`, `update`, `delete`, `sync`, `override`,
`action/<group/kind/action-name>`
#### Application resources
The resource path for application objects is of the form
`<project-name>/<application-name>`.
Delete access to sub-resources of a project, such as a rollout or a pod, cannot
be managed granularly. `<project-name>/<application-name>` grants access to all
subresources of an application.
#### The `action` action
The `action` action corresponds to either built-in resource customizations defined
[in the Argo CD repository](https://github.com/argoproj/argo-cd/search?q=filename%3Aaction.lua+path%3Aresource_customizations),
or to [custom resource actions](resource_actions.md#custom-resource-actions) defined by you.
The `action` path is of the form `action/<api-group>/<Kind>/<action-name>`. For
example, a resource customization path
`resource_customizations/extensions/DaemonSet/actions/restart/action.lua`
corresponds to the `action` path `action/extensions/DaemonSet/restart`. You can
also use glob patterns in the action path: `action/*` (or regex patterns if you have
[enabled the `regex` match mode](https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/argocd-rbac-cm.yaml)).
#### `exec` resource
@@ -42,7 +64,8 @@ they have `create` privileges. If the Pod mounts a ServiceAccount token (which i
then the user effectively has the same privileges as that ServiceAccount.
The exec feature is disabled entirely by default. To enable it, set the `exec.enabled` key to "true" on the argocd-cm
ConfigMap. You will also need to add the following to the argocd-api-server Role or ClusterRole.
ConfigMap. You will also need to add the following to the argocd-api-server Role (if you're using Argo CD in namespaced
mode) or ClusterRole (if you're using Argo CD in cluster mode).
```yaml
- apiGroups:
@@ -78,7 +101,7 @@ data:
p, role:org-admin, repositories, update, *, allow
p, role:org-admin, repositories, delete, *, allow
p, role:org-admin, logs, get, *, allow
p, role:org-admin, exec, get, *, allow
p, role:org-admin, exec, create, */*, allow
g, your-github-org:your-team, role:org-admin
```
@@ -94,12 +117,12 @@ p, role:staging-db-admins, applications, override, staging-db-admins/*, allow
p, role:staging-db-admins, applications, sync, staging-db-admins/*, allow
p, role:staging-db-admins, applications, update, staging-db-admins/*, allow
p, role:staging-db-admins, logs, get, staging-db-admins/*, allow
p, role:staging-db-admins, exec, get, staging-db-admins/*, allow
p, role:staging-db-admins, exec, create, staging-db-admins/*, allow
p, role:staging-db-admins, projects, get, staging-db-admins, allow
g, db-admins, role:staging-db-admins
```
This example defines a *role* called `staging-db-admins` with *eight permissions* that allow that role to perform the *actions* (`create`/`delete`/`get`/`override`/`sync`/`update` applications, `get` logs, `get` exec and `get` appprojects) against `*` (all) objects in the `staging-db-admins` Argo CD AppProject.
This example defines a *role* called `staging-db-admins` with *eight permissions* that allow that role to perform the *actions* (`create`/`delete`/`get`/`override`/`sync`/`update` applications, `get` logs, `create` exec and `get` appprojects) against `*` (all) objects in the `staging-db-admins` Argo CD AppProject.
## Anonymous Access

View File

@@ -119,6 +119,13 @@ kubectl delete secret argocd-manager-token-XXXXXX -n kube-system
argocd cluster add CONTEXTNAME
```
!!! note
Kubernetes 1.24 [stopped automatically creating tokens for Service Accounts](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.24.md#no-really-you-must-read-this-before-you-upgrade).
[Starting in Argo CD 2.4](https://github.com/argoproj/argo-cd/pull/9546), `argocd cluster add` creates a
ServiceAccount _and_ a non-expiring Service Account token Secret when adding 1.24 clusters. In the future, Argo CD
will [add support for the Kubernetes TokenRequest API](https://github.com/argoproj/argo-cd/issues/9610) to avoid
using long-lived tokens.
To revoke Argo CD's access to a managed cluster, delete the RBAC artifacts against the *_managed_*
cluster, and remove the cluster entry from Argo CD:
@@ -210,4 +217,9 @@ Argo CD logs payloads of most API requests except request that are considered se
can be found in [server/server.go](https://github.com/argoproj/argo-cd/blob/abba8dddce8cd897ba23320e3715690f465b4a95/server/server.go#L516).
Argo CD does not log IP addresses of clients requesting API endpoints, since the API server is typically behind a proxy. Instead, it is recommended
to configure IP addresses logging in the proxy server that sits in front of the API server.
to configure IP addresses logging in the proxy server that sits in front of the API server.
## ApplicationSets
Argo CD's ApplicationSets feature has its own [security considerations](./applicationset/Security.md). Be aware of those
issues before using ApplicationSets.

View File

@@ -37,6 +37,7 @@ argocd-application-controller [flags]
--metrics-port int Start metrics server on given port (default 8082)
-n, --namespace string If present, the namespace scope for this CLI request
--operation-processors int Number of application operation processors (default 10)
--otlp-address string OpenTelemetry collector address to send traces to
--password string Password for basic authentication to the API server
--redis string Redis server hostname and port (e.g. argocd-redis:6379).
--redis-ca-certificate string Path to Redis server CA certificate (e.g. /etc/certs/redis/ca.crt). If not specified, system trusted CAs will be used for server certificate validation.

View File

@@ -6,8 +6,11 @@ The Argo CD Notifications and ApplicationSet are part of Argo CD now. You no lon
The Notifications and ApplicationSet components are bundled into default Argo CD installation manifests.
The bundled manifests are drop-in replacements for the previous versions. If you are using Kustomize to bundle the manifests together then just
remove references to https://github.com/argoproj-labs/argocd-notifications and https://github.com/argoproj-labs/applicationset. No action is required
if you are using `kubectl apply`.
remove references to https://github.com/argoproj-labs/argocd-notifications and https://github.com/argoproj-labs/applicationset.
If you are using [the argocd-notifications helm chart](https://github.com/argoproj/argo-helm/tree/argocd-notifications-1.8.1/charts/argocd-notifications), you can move the chart [values](https://github.com/argoproj/argo-helm/blob/argocd-notifications-1.8.1/charts/argocd-notifications/values.yaml) to the `notifications` section of the argo-cd chart [values](https://github.com/argoproj/argo-helm/blob/main/charts/argo-cd/values.yaml#L2152). Although most values remain as is, for details please look up the values that are relevant to you.
No action is required if you are using `kubectl apply`.
## Configure Additional Argo CD Binaries
@@ -42,4 +45,4 @@ Note that bundled Kustomize version has been upgraded from 4.2.0 to 4.4.1.
## Upgraded Helm Version
Note that bundled Helm version has been upgraded from 3.7.1 to 3.8.0.
Note that bundled Helm version has been upgraded from 3.7.1 to 3.8.0.

View File

@@ -2,20 +2,92 @@
## KSonnet support is removed
The [https://ksonnet.io/] had been deprecated in [2019](https://github.com/ksonnet/ksonnet/pull/914/files) and no longer maintained.
The time has come to remove it from the ArgoCD.
Ksonnet was deprecated in [2019](https://github.com/ksonnet/ksonnet/pull/914/files) and is no longer maintained.
The time has come to remove it from the Argo CD.
## Helm 2 support is removed
Helm 2 is not been officially supported since [Nov 2020](https://helm.sh/blog/helm-2-becomes-unsupported/). In order to ensure a smooth transition
Helm 2 support was preserved in the Argo CD. We feel that Helm 3 is stable and it is time to drop Helm 2 support.
Helm 2 has not been officially supported since [Nov 2020](https://helm.sh/blog/helm-2-becomes-unsupported/). In order to ensure a smooth transition,
Helm 2 support was preserved in the Argo CD. We feel that Helm 3 is stable, and it is time to drop Helm 2 support.
## Support for private repo SSH keys using the SHA-1 signature hash algorithm is removed
Argo CD 2.4 upgraded its base image from Ubuntu 20.04 to Ubuntu 22.04, which upgraded OpenSSH to 8.9. OpenSSH starting
with 8.8 [dropped support for the `ssh-rsa` SHA-1 key signature algorithm](https://www.openssh.com/txt/release-8.8).
The signature algorithm is _not_ the same as the algorithm used when generating the key. There is no need to update
keys.
The signature algorithm is negotiated with the SSH server when the connection is being set up. The client offers its
list of accepted signature algorithms, and if the server has a match, the connection proceeds. For most SSH servers on
up-to-date git providers, acceptable algorithms other than `ssh-rsa` should be available.
Before upgrading to Argo CD 2.4, check whether your git provider(s) using SSH authentication support algorithms newer
than `rsa-ssh`.
1. Make sure your version of SSH >= 8.9 (the version used by Argo CD). If not, upgrade it before proceeding.
```shell
ssh -V
```
Example output: `OpenSSH_8.9p1 Ubuntu-3, OpenSSL 3.0.2 15 Mar 2022`
2. Once you have a recent version of OpenSSH, follow the directions from the [OpenSSH 8.8 release notes](https://www.openssh.com/txt/release-8.7):
> To check whether a server is using the weak ssh-rsa public key
> algorithm, for host authentication, try to connect to it after
> removing the ssh-rsa algorithm from ssh(1)'s allowed list:
>
> ```shell
> ssh -oHostKeyAlgorithms=-ssh-rsa user@host
> ```
>
> If the host key verification fails and no other supported host key
> types are available, the server software on that host should be
> upgraded.
If the server does not support an acceptable version, you will get an error similar to this;
```
$ ssh -oHostKeyAlgorithms=-ssh-rsa vs-ssh.visualstudio.com
Unable to negotiate with 20.42.134.1 port 22: no matching host key type found. Their offer: ssh-rsa
```
This indicates that the server needs to update its supported key signature algorithms, and Argo CD will not connect
to it.
### Workaround
The [OpenSSH 8.8 release notes](https://www.openssh.com/txt/release-8.8) describe a workaround if you cannot change the
server's key signature algorithms configuration.
> Incompatibility is more likely when connecting to older SSH
> implementations that have not been upgraded or have not closely tracked
> improvements in the SSH protocol. For these cases, it may be necessary
> to selectively re-enable RSA/SHA1 to allow connection and/or user
> authentication via the HostkeyAlgorithms and PubkeyAcceptedAlgorithms
> options. For example, the following stanza in ~/.ssh/config will enable
> RSA/SHA1 for host and user authentication for a single destination host:
>
> ```
> Host old-host
> HostkeyAlgorithms +ssh-rsa
> PubkeyAcceptedAlgorithms +ssh-rsa
> ```
>
> We recommend enabling RSA/SHA1 only as a stopgap measure until legacy
> implementations can be upgraded or reconfigured with another key type
> (such as ECDSA or Ed25519).
To apply this to Argo CD, you could create a ConfigMap with the desired ssh config file and then mount it at
`/home/argocd/.ssh/config`.
## Configure RBAC to account for new `exec` resource
2.4 introduces a new `exec` [RBAC resource](https://argo-cd.readthedocs.io/en/stable/operator-manual/rbac/#rbac-resources-and-actions).
When you upgrade to 2.4, RBAC policies with `*` in the resource field and `create` or `*` in the verb field will automatically grant the `exec` privilege.
When you upgrade to 2.4, RBAC policies with `*` in the resource field and `create` or `*` in the action field will automatically grant the `exec` privilege.
To avoid granting the new privilege, replace the existing policy with a list of new policies explicitly listing the old resources.
@@ -42,12 +114,61 @@ p, role: org-admin, accounts, create, my-proj/*, allow
p, role: org-admin, gpgkeys, create, my-proj/*, allow
```
## Remove the shared volume from any sidecar plugins
## Enable logs RBAC enforcement
2.4 introduced `logs` as a new RBAC resource. In 2.3, users with `applications, get` access automatically get logs
access. In 2.5, you will have to explicitly grant `logs, get` access. Logs RBAC enforcement can be enabled with a flag
in 2.4. We recommend enabling the flag now for an easier upgrade experience in 2.5.
To enabled logs RBAC enforcement, add this to your argocd-cm ConfigMap:
```yaml
server.rbac.log.enforce.enable: "true"
```
If you want to allow the same users to continue to have logs access, just find every line that grants
`applications, get` access and also grant `logs, get`.
### Example
Old:
```csv
p, role:staging-db-admins, applications, get, staging-db-admins/*, allow
p, role:test-db-admins, applications, *, staging-db-admins/*, allow
```
New:
```csv
p, role:staging-db-admins, applications, get, staging-db-admins/*, allow
p, role:staging-db-admins, logs, get, staging-db-admins/*, allow
p, role:test-db-admins, applications, *, staging-db-admins/*, allow
p, role:test-db-admins, logs, get, staging-db-admins/*, allow
```
## Known UI issue
Currently, upon pressing the "LOGS" tab in pod view by users who don't have an explicit allow get logs policy, the red "unable to load data: Internal error" is received in the bottom of the screen, and "Failed to load data, please try again" is displayed.
## Test repo-server with its new dedicated Service Account
As a security enhancement, the argocd-repo-server Deployment uses its own Service Account instead of `default`.
If you have a custom environment that might depend on repo-server using the `default` Service Account (such as a plugin
that uses the Service Account for auth), be sure to test before deploying the 2.4 upgrade to production.
## Plugins
### Remove the shared volume from any sidecar plugins
As a security enhancement, [sidecar plugins](../../user-guide/config-management-plugins.md#option-2-configure-plugin-via-sidecar)
no longer share the /tmp directory with the repo-server.
If you have one or more sidecar plugins enabled, remove the /tmp volume mount from the plugin container definition.
If you have one or more sidecar plugins enabled, replace the /tmp volume mount for each sidecar to use a volume specific
to each plugin.
```yaml
apiVersion: apps/v1
@@ -60,7 +181,47 @@ spec:
containers:
- name: your-plugin-name
volumeMounts:
# Remove the next two lines:
- mountPath: /tmp
name: tmp
name: your-plugin-name-tmp
volumes:
# Add this volume.
- name: your-plugin-name-tmp
emptyDir: {}
```
### Update plugins to use newly-prefixed environment variables
If you use plugins that depend on user-supplied environment variables, then they must be updated to be compatible with
Argo CD 2.4. Here is an example of user-supplied environment variables in the `plugin` section of an Application spec:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
spec:
source:
plugin:
env:
- name: FOO
value: bar
```
Going forward, all user-supplied environment variables will be prefixed with `ARGOCD_ENV_` before being sent to the
plugin's `init`, `generate`, or `discover` commands. This prevents users from setting potentially-sensitive environment
variables.
If you have written a custom plugin which handles user-provided environment variables, update it to handle the new
prefix.
If you use a third-party plugin which does not explicitly advertise Argo CD 2.4 support, it might not handle the
prefixed environment variables. Open an issue with the plugin's authors and confirm support before upgrading to Argo CD
2.4.
### Confirm sidecar plugins have all necessary environment variables
A bug in < 2.4 caused `init` and `generate` commands to receive environment variables from the main repo-server
container, taking precedence over environment variables from the plugin's sidecar.
Starting in 2.4, sidecar plugins will not receive environment variables from the main repo-server container. Make sure
that any environment variables necessary for the sidecar plugin to function are set on the sidecar plugin.
argocd-cm plugins will continue to receive environment variables from the main repo-server container.

View File

@@ -50,7 +50,7 @@ If you've never configured this, you'll be redirected straight to this if you tr
### Configure Argo to use OpenID Connect
Edit `argo-cm` and add the following `dex.config` to the data section, replacing `clientID` and `clientSecret` with the values you saved before:
Edit `argocd-cm` and add the following `dex.config` to the data section, replacing `clientID` and `clientSecret` with the values you saved before:
```yaml
data:
@@ -113,7 +113,7 @@ data:
### Configure Argo to use the new Google SAML App
Edit `argo-cm` and add the following `dex.config` to the data section, replacing the `caData`, `argocd.example.com`, `sso-url`, and optionally `google-entity-id` with your values from the Google SAML App:
Edit `argocd-cm` and add the following `dex.config` to the data section, replacing the `caData`, `argocd.example.com`, `sso-url`, and optionally `google-entity-id` with your values from the Google SAML App:
```yaml
data:
@@ -211,7 +211,7 @@ Go through the same steps as in [OpenID Connect using Dex](#openid-connect-using
defaultMode: 420
secretName: argocd-google-groups-json
3. Edit `argo-cm` and add the following `dex.config` to the data section, replacing `clientID` and `clientSecret` with the values you saved before, `adminEmail` with the address for the admin user you're going to impersonate, and editing `redirectURI` with your Argo CD domain:
3. Edit `argocd-cm` and add the following `dex.config` to the data section, replacing `clientID` and `clientSecret` with the values you saved before, `adminEmail` with the address for the admin user you're going to impersonate, and editing `redirectURI` with your Argo CD domain:
dex.config: |
connectors:

View File

@@ -4,6 +4,10 @@ The [ApplicationSet controller](../operator-manual/applicationset/index.md) is a
The set of tools provided by the ApplicationSet controller may also be used to allow developers (without access to the Argo CD namespace) to independently create Applications without cluster-administrator intervention.
!!! warning
Be aware of the [security implications](../operator-manual/applicationset/Security.md) before allowing developers to
create Applications via ApplicationSets.
The ApplicationSet controller is installed alongside Argo CD (within the same namespace), and the controller automatically generates Argo CD Applications based on the contents of a new `ApplicationSet` Custom Resource (CR).
Here is an example of an `ApplicationSet` resource that can be used to target an Argo CD Application to multiple clusters:

View File

@@ -2,6 +2,11 @@
Argo CD allows integrating more config management tools using config management plugins.
!!! warning
Plugins are granted a level of trust in the Argo CD system, so it is important to implement plugins securely. Argo
CD administrators should only install plugins from trusted sources, and they should audit plugins to weigh their
particular risks and benefits.
## Installing a CMP
There are two ways to install a Config Management Plugin (CMP):
@@ -62,8 +67,6 @@ spec:
command: [sh, -c, 'echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"Foo\": \"$FOO\", \"KubeVersion\": \"$KUBE_VERSION\", \"KubeApiVersion\": \"$KUBE_API_VERSIONS\",\"Bar\": \"baz\"}}}"']
discover:
fileName: "./subdir/s*.yaml"
allowConcurrency: true
lockRepo: false
```
!!! note
@@ -85,9 +88,6 @@ If `discover.fileName` is not provided, the `discover.find.command` is executed
application repository is supported by the plugin or not. The `find` command should return a non-error exit code
and produce output to stdout when the application source type is supported.
If your plugin makes use of `git` (e.g. `git crypt`), it is advised to set `lockRepo` to `true` so that your plugin will have exclusive access to the
repository at the time it is executed. Otherwise, two applications synced at the same time may result in a race condition and sync failure.
#### 2. Place the plugin configuration file in the sidecar
Argo CD expects the plugin configuration file to be located at `/home/argocd/cmp-server/config/plugin.yaml` in the sidecar.
@@ -114,8 +114,6 @@ data:
command: [sh, -c, 'echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"Foo\": \"$FOO\", \"KubeVersion\": \"$KUBE_VERSION\", \"KubeApiVersion\": \"$KUBE_API_VERSIONS\",\"Bar\": \"baz\"}}}"']
discover:
fileName: "./subdir/s*.yaml"
allowConcurrency: true
lockRepo: false
```
#### 3. Register the plugin sidecar
@@ -140,10 +138,16 @@ containers:
- mountPath: /home/argocd/cmp-server/config/plugin.yaml
subPath: plugin.yaml
name: cmp-plugin
volumes:
- configMap:
name: cmp-plugin
# Starting with v2.4, do NOT mount the same tmp volume as the repo-server container. The filesystem separation helps
# mitigate path traversal attacks.
- mountPath: /tmp
name: cmp-tmp
volumes:
- configMap:
name: cmp-plugin
name: cmp-plugin
- emptyDir: {}
name: cmp-tmp
```
!!! important "Double-check these items"
@@ -155,7 +159,7 @@ containers:
CMP commands have access to
1. The system environment variables
1. The system environment variables (of the repo-server container for argocd-cm plugins or of the sidecar for sidecar plugins)
2. [Standard build environment](build-environment.md)
3. Variables in the application spec (References to system and build variables will get interpolated in the variables' values):
@@ -172,6 +176,19 @@ spec:
value: test-$ARGOCD_APP_REVISION
```
!!! note
The `discover.command` command only has access to the above environment starting with v2.4.
> v2.4
Before reaching the `init.command`, `generate.command`, and `discover.command` commands, Argo CD prefixes all
user-supplied environment variables (#3 above) with `ARGOCD_ENV_`. This prevents users from directly setting
potentially-sensitive environment variables.
If your plugin was written before 2.4 and depends on user-supplied environment variables, then you will need to update
your plugin's behavior to work with 2.4. If you use a third-party plugin, make sure they explicitly advertise support
for 2.4.
## Using a CMP
If your CMP is defined in the `argocd-cm` ConfigMap, you can create a new Application using the CLI. Replace

View File

@@ -83,7 +83,13 @@ Private repositories that require an SSH private key have a URL that typically s
> v1.2 or later
You can configure your Git repository using HTTPS either using the CLI or the UI.
You can configure your Git repository using SSH either using the CLI or the UI.
!!! note
Argo CD 2.4 upgraded to OpenSSH 8.9. OpenSSH 8.8
[dropped support for the `ssh-rsa` SHA-1 key signature algorithm](https://www.openssh.com/txt/release-8.8).
See the [2.3 to 2.4 upgrade guide](../operator-manual/upgrading/2.3-2.4.md) for details about testing SSH servers
for compatibility with Argo CD and for working around servers that do not support newer algorithms.
Using the CLI:

9
go.mod
View File

@@ -9,8 +9,8 @@ require (
github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d
github.com/alicebob/miniredis v2.5.0+incompatible
github.com/alicebob/miniredis/v2 v2.14.2
github.com/argoproj/gitops-engine v0.6.1-0.20220328190556-73bcea9c8c8f
github.com/argoproj/notifications-engine v0.3.1-0.20220322174744-ac18ca10234c
github.com/argoproj/gitops-engine v0.7.0
github.com/argoproj/notifications-engine v0.3.1-0.20220430155844-567361917320
github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0
github.com/aws/aws-sdk-go v1.38.49
github.com/bombsimon/logrusr/v2 v2.0.1
@@ -75,7 +75,7 @@ require (
github.com/whilp/git-urls v0.0.0-20191001220047-6db9661140c0
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-20210817164053-32db794688a5
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
golang.org/x/net v0.0.0-20211209124913-491a49abca63
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
@@ -217,7 +217,7 @@ require (
gopkg.in/square/go-jose.v2 v2.2.2 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
k8s.io/apiserver v0.23.1 // indirect
k8s.io/apiserver v0.23.1
k8s.io/cli-runtime v0.23.1 // indirect
k8s.io/component-base v0.23.1 // indirect
k8s.io/component-helpers v0.23.1 // indirect
@@ -237,6 +237,7 @@ require (
)
require (
github.com/PagerDuty/go-pagerduty v1.5.0 // indirect
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect

13
go.sum
View File

@@ -104,6 +104,8 @@ github.com/Microsoft/hcsshim v0.8.22/go.mod h1:91uVCVzvX2QD16sMCenoxxXo6L1wJnLMX
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PagerDuty/go-pagerduty v1.5.0 h1:/p8FGD32G8HGm7MQIjlTPTGXRJ62Qkm8Lmt5BcUVJOo=
github.com/PagerDuty/go-pagerduty v1.5.0/go.mod h1:txr8VbObXdk2RkqF+C2an4qWssdGY99fK26XYUDjh+4=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@@ -144,10 +146,10 @@ github.com/antonmedv/expr v1.8.9/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmH
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/appscode/go v0.0.0-20190808133642-1d4ef1f1c1e0/go.mod h1:iy07dV61Z7QQdCKJCIvUoDL21u6AIceRhZzyleh2ymc=
github.com/argoproj/gitops-engine v0.6.1-0.20220328190556-73bcea9c8c8f h1:3x8pG690gbZtGK2G/dlL7b243t/WyyeDT7OUs2n76Nk=
github.com/argoproj/gitops-engine v0.6.1-0.20220328190556-73bcea9c8c8f/go.mod h1:pRgVpLW7pZqf7n3COJ7UcDepk4cI61LAcJd64Q3Jq/c=
github.com/argoproj/notifications-engine v0.3.1-0.20220322174744-ac18ca10234c h1:n/5BIocdWYtp1qC8/GFgUUV62I+gln54KFZZLgczwDc=
github.com/argoproj/notifications-engine v0.3.1-0.20220322174744-ac18ca10234c/go.mod h1:QF4tr3wfWOnhkKSaRpx7k/KEErQAh8iwKQ2pYFu/SfA=
github.com/argoproj/gitops-engine v0.7.0 h1:X6W8VP9bWTe74wWxAV3i8KZ0yBmre5DU8g+GWH09FCo=
github.com/argoproj/gitops-engine v0.7.0/go.mod h1:pRgVpLW7pZqf7n3COJ7UcDepk4cI61LAcJd64Q3Jq/c=
github.com/argoproj/notifications-engine v0.3.1-0.20220430155844-567361917320 h1:XDjtTfccs4rSOT1n+i1zV9RpxQdKky1b4YBic16E0qY=
github.com/argoproj/notifications-engine v0.3.1-0.20220430155844-567361917320/go.mod h1:R3zlopt+/juYlebQc9Jarn9vBQ2xZruWOWjUNkfGY9M=
github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 h1:Cfp7rO/HpVxnwlRqJe0jHiBbZ77ZgXhB6HWlYD02Xdc=
github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0/go.mod h1:ra+bQPmbVAoEL+gYSKesuigt4m49i3Qa3mE/xQcjCiA=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -1228,8 +1230,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=

View File

@@ -119,6 +119,12 @@ spec:
name: argocd-cmd-params-cm
key: controller.default.cache.expiration
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_OTLP_ADDRESS
valueFrom:
configMapKeyRef:
name: argocd-cmd-params-cm
key: otlp.address
optional: true
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-application-controller
@@ -178,4 +184,4 @@ spec:
- key: tls.key
path: tls.key
- key: ca.crt
path: ca.crt
path: ca.crt

View File

@@ -5,7 +5,7 @@ kind: Kustomization
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: latest
newTag: v2.4.0
resources:
- ./application-controller
- ./dex

View File

@@ -15,16 +15,13 @@ spec:
labels:
app.kubernetes.io/name: argocd-repo-server
spec:
serviceAccountName: argocd-repo-server
automountServiceAccountToken: false
containers:
- name: argocd-repo-server
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
command:
- entrypoint.sh
- argocd-repo-server
- --redis
- $(ARGOCD_REDIS_SERVICE):6379
command: [ "sh", "-c", "entrypoint.sh argocd-repo-server --redis $(ARGOCD_REDIS_SERVICE):6379"]
env:
- name: ARGOCD_RECONCILIATION_TIMEOUT
valueFrom:
@@ -98,6 +95,12 @@ spec:
name: argocd-cmd-params-cm
key: reposerver.default.cache.expiration
optional: true
- name: ARGOCD_REPO_SERVER_OTLP_ADDRESS
valueFrom:
configMapKeyRef:
name: argocd-cmd-params-cm
key: otlp.address
optional: true
- name: HELM_CACHE_HOME
value: /helm-working-dir
- name: HELM_CONFIG_HOME

View File

@@ -0,0 +1,8 @@
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/name: argocd-repo-server
app.kubernetes.io/part-of: argocd
app.kubernetes.io/component: repo-server
name: argocd-repo-server

View File

@@ -2,6 +2,7 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- argocd-repo-server-sa.yaml
- argocd-repo-server-deployment.yaml
- argocd-repo-server-service.yaml
- argocd-repo-server-network-policy.yaml
- argocd-repo-server-network-policy.yaml

View File

@@ -178,6 +178,12 @@ spec:
name: argocd-cmd-params-cm
key: server.http.cookie.maxnumber
optional: true
- name: ARGOCD_SERVER_OTLP_ADDRESS
valueFrom:
configMapKeyRef:
name: argocd-cmd-params-cm
key: otlp.address
optional: true
volumeMounts:
- name: ssh-known-hosts
mountPath: /app/config/ssh

View File

@@ -9017,6 +9017,15 @@ metadata:
app.kubernetes.io/part-of: argocd
name: argocd-redis
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/component: repo-server
app.kubernetes.io/name: argocd-repo-server
app.kubernetes.io/part-of: argocd
name: argocd-repo-server
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
@@ -9376,7 +9385,7 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -9506,10 +9515,9 @@ spec:
automountServiceAccountToken: false
containers:
- command:
- entrypoint.sh
- argocd-repo-server
- --redis
- argocd-redis:6379
- sh
- -c
- entrypoint.sh argocd-repo-server --redis argocd-redis:6379
env:
- name: ARGOCD_RECONCILIATION_TIMEOUT
valueFrom:
@@ -9583,13 +9591,19 @@ spec:
key: reposerver.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_OTLP_ADDRESS
valueFrom:
configMapKeyRef:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
- name: HELM_CACHE_HOME
value: /helm-working-dir
- name: HELM_CONFIG_HOME
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -9638,7 +9652,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -9650,6 +9664,7 @@ spec:
volumeMounts:
- mountPath: /var/run/argocd
name: var-files
serviceAccountName: argocd-repo-server
volumes:
- configMap:
name: argocd-ssh-known-hosts-cm
@@ -9818,7 +9833,13 @@ spec:
key: controller.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
- name: ARGOCD_APPLICATION_CONTROLLER_OTLP_ADDRESS
valueFrom:
configMapKeyRef:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -12,4 +12,4 @@ resources:
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: latest
newTag: v2.4.0

View File

@@ -11,7 +11,7 @@ patchesStrategicMerge:
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: latest
newTag: v2.4.0
resources:
- ../../base/application-controller
- ../../base/applicationset-controller

View File

@@ -9042,6 +9042,15 @@ metadata:
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/component: repo-server
app.kubernetes.io/name: argocd-repo-server
app.kubernetes.io/part-of: argocd
name: argocd-repo-server
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/component: server
@@ -10311,7 +10320,7 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -10408,7 +10417,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -10448,7 +10457,7 @@ spec:
containers:
- command:
- argocd-notifications
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -10681,13 +10690,19 @@ spec:
key: reposerver.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_OTLP_ADDRESS
valueFrom:
configMapKeyRef:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
- name: HELM_CACHE_HOME
value: /helm-working-dir
- name: HELM_CONFIG_HOME
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -10736,7 +10751,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -10748,6 +10763,7 @@ spec:
volumeMounts:
- mountPath: /var/run/argocd
name: var-files
serviceAccountName: argocd-repo-server
volumes:
- configMap:
name: argocd-ssh-known-hosts-cm
@@ -10976,7 +10992,13 @@ spec:
key: server.http.cookie.maxnumber
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
- name: ARGOCD_SERVER_OTLP_ADDRESS
valueFrom:
configMapKeyRef:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -11178,7 +11200,13 @@ spec:
key: controller.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
- name: ARGOCD_APPLICATION_CONTROLLER_OTLP_ADDRESS
valueFrom:
configMapKeyRef:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -51,6 +51,15 @@ metadata:
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/component: repo-server
app.kubernetes.io/name: argocd-repo-server
app.kubernetes.io/part-of: argocd
name: argocd-repo-server
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/component: server
@@ -1235,7 +1244,7 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -1332,7 +1341,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -1372,7 +1381,7 @@ spec:
containers:
- command:
- argocd-notifications
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -1605,13 +1614,19 @@ spec:
key: reposerver.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_OTLP_ADDRESS
valueFrom:
configMapKeyRef:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
- name: HELM_CACHE_HOME
value: /helm-working-dir
- name: HELM_CONFIG_HOME
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -1660,7 +1675,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -1672,6 +1687,7 @@ spec:
volumeMounts:
- mountPath: /var/run/argocd
name: var-files
serviceAccountName: argocd-repo-server
volumes:
- configMap:
name: argocd-ssh-known-hosts-cm
@@ -1900,7 +1916,13 @@ spec:
key: server.http.cookie.maxnumber
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
- name: ARGOCD_SERVER_OTLP_ADDRESS
valueFrom:
configMapKeyRef:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2102,7 +2124,13 @@ spec:
key: controller.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
- name: ARGOCD_APPLICATION_CONTROLLER_OTLP_ADDRESS
valueFrom:
configMapKeyRef:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -9033,6 +9033,15 @@ metadata:
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/component: repo-server
app.kubernetes.io/name: argocd-repo-server
app.kubernetes.io/part-of: argocd
name: argocd-repo-server
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/component: server
@@ -9683,7 +9692,7 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -9780,7 +9789,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -9820,7 +9829,7 @@ spec:
containers:
- command:
- argocd-notifications
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -9945,10 +9954,9 @@ spec:
automountServiceAccountToken: false
containers:
- command:
- entrypoint.sh
- argocd-repo-server
- --redis
- argocd-redis:6379
- sh
- -c
- entrypoint.sh argocd-repo-server --redis argocd-redis:6379
env:
- name: ARGOCD_RECONCILIATION_TIMEOUT
valueFrom:
@@ -10022,13 +10030,19 @@ spec:
key: reposerver.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_OTLP_ADDRESS
valueFrom:
configMapKeyRef:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
- name: HELM_CACHE_HOME
value: /helm-working-dir
- name: HELM_CONFIG_HOME
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -10077,7 +10091,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -10089,6 +10103,7 @@ spec:
volumeMounts:
- mountPath: /var/run/argocd
name: var-files
serviceAccountName: argocd-repo-server
volumes:
- configMap:
name: argocd-ssh-known-hosts-cm
@@ -10313,7 +10328,13 @@ spec:
key: server.http.cookie.maxnumber
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
- name: ARGOCD_SERVER_OTLP_ADDRESS
valueFrom:
configMapKeyRef:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -10509,7 +10530,13 @@ spec:
key: controller.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
- name: ARGOCD_APPLICATION_CONTROLLER_OTLP_ADDRESS
valueFrom:
configMapKeyRef:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -42,6 +42,15 @@ metadata:
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/component: repo-server
app.kubernetes.io/name: argocd-repo-server
app.kubernetes.io/part-of: argocd
name: argocd-repo-server
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/component: server
@@ -607,7 +616,7 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -704,7 +713,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -744,7 +753,7 @@ spec:
containers:
- command:
- argocd-notifications
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -869,10 +878,9 @@ spec:
automountServiceAccountToken: false
containers:
- command:
- entrypoint.sh
- argocd-repo-server
- --redis
- argocd-redis:6379
- sh
- -c
- entrypoint.sh argocd-repo-server --redis argocd-redis:6379
env:
- name: ARGOCD_RECONCILIATION_TIMEOUT
valueFrom:
@@ -946,13 +954,19 @@ spec:
key: reposerver.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_OTLP_ADDRESS
valueFrom:
configMapKeyRef:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
- name: HELM_CACHE_HOME
value: /helm-working-dir
- name: HELM_CONFIG_HOME
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -1001,7 +1015,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.4.0
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -1013,6 +1027,7 @@ spec:
volumeMounts:
- mountPath: /var/run/argocd
name: var-files
serviceAccountName: argocd-repo-server
volumes:
- configMap:
name: argocd-ssh-known-hosts-cm
@@ -1237,7 +1252,13 @@ spec:
key: server.http.cookie.maxnumber
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
- name: ARGOCD_SERVER_OTLP_ADDRESS
valueFrom:
configMapKeyRef:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -1433,7 +1454,13 @@ spec:
key: controller.default.cache.expiration
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
- name: ARGOCD_APPLICATION_CONTROLLER_OTLP_ADDRESS
valueFrom:
configMapKeyRef:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.0
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -64,6 +64,7 @@ nav:
- operator-manual/notifications/services/mattermost.md
- operator-manual/notifications/services/opsgenie.md
- operator-manual/notifications/services/overview.md
- operator-manual/notifications/services/pagerduty.md
- operator-manual/notifications/services/pushover.md
- operator-manual/notifications/services/rocketchat.md
- operator-manual/notifications/services/slack.md
@@ -75,6 +76,7 @@ nav:
- Introduction: operator-manual/applicationset/index.md
- Installations: operator-manual/applicationset/Getting-Started.md
- Use Cases: operator-manual/applicationset/Use-Cases.md
- Security: operator-manual/applicationset/Security.md
- How ApplicationSet controller interacts with Argo CD: operator-manual/applicationset/Argo-CD-Integration.md
- Generators:
- operator-manual/applicationset/Generators.md

View File

@@ -238,9 +238,9 @@ func (m *RevisionMetadataQuery) GetRevision() string {
// ApplicationEventsQuery is a query for application resource events
type ApplicationResourceEventsQuery struct {
Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
ResourceNamespace *string `protobuf:"bytes,2,req,name=resourceNamespace" json:"resourceNamespace,omitempty"`
ResourceName *string `protobuf:"bytes,3,req,name=resourceName" json:"resourceName,omitempty"`
ResourceUID *string `protobuf:"bytes,4,req,name=resourceUID" json:"resourceUID,omitempty"`
ResourceNamespace *string `protobuf:"bytes,2,opt,name=resourceNamespace" json:"resourceNamespace,omitempty"`
ResourceName *string `protobuf:"bytes,3,opt,name=resourceName" json:"resourceName,omitempty"`
ResourceUID *string `protobuf:"bytes,4,opt,name=resourceUID" json:"resourceUID,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -951,7 +951,7 @@ func (m *ApplicationRollbackRequest) GetPrune() bool {
type ApplicationResourceRequest struct {
Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
Namespace *string `protobuf:"bytes,2,req,name=namespace" json:"namespace,omitempty"`
Namespace *string `protobuf:"bytes,2,opt,name=namespace" json:"namespace,omitempty"`
ResourceName *string `protobuf:"bytes,3,req,name=resourceName" json:"resourceName,omitempty"`
Version *string `protobuf:"bytes,4,req,name=version" json:"version,omitempty"`
Group *string `protobuf:"bytes,5,opt,name=group" json:"group,omitempty"`
@@ -1038,7 +1038,7 @@ func (m *ApplicationResourceRequest) GetKind() string {
type ApplicationResourcePatchRequest struct {
Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
Namespace *string `protobuf:"bytes,2,req,name=namespace" json:"namespace,omitempty"`
Namespace *string `protobuf:"bytes,2,opt,name=namespace" json:"namespace,omitempty"`
ResourceName *string `protobuf:"bytes,3,req,name=resourceName" json:"resourceName,omitempty"`
Version *string `protobuf:"bytes,4,req,name=version" json:"version,omitempty"`
Group *string `protobuf:"bytes,5,opt,name=group" json:"group,omitempty"`
@@ -1141,7 +1141,7 @@ func (m *ApplicationResourcePatchRequest) GetPatchType() string {
type ApplicationResourceDeleteRequest struct {
Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
Namespace *string `protobuf:"bytes,2,req,name=namespace" json:"namespace,omitempty"`
Namespace *string `protobuf:"bytes,2,opt,name=namespace" json:"namespace,omitempty"`
ResourceName *string `protobuf:"bytes,3,req,name=resourceName" json:"resourceName,omitempty"`
Version *string `protobuf:"bytes,4,req,name=version" json:"version,omitempty"`
Group *string `protobuf:"bytes,5,opt,name=group" json:"group,omitempty"`
@@ -1244,7 +1244,7 @@ func (m *ApplicationResourceDeleteRequest) GetOrphan() bool {
type ResourceActionRunRequest struct {
Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
Namespace *string `protobuf:"bytes,2,req,name=namespace" json:"namespace,omitempty"`
Namespace *string `protobuf:"bytes,2,opt,name=namespace" json:"namespace,omitempty"`
ResourceName *string `protobuf:"bytes,3,req,name=resourceName" json:"resourceName,omitempty"`
Version *string `protobuf:"bytes,4,req,name=version" json:"version,omitempty"`
Group *string `protobuf:"bytes,5,opt,name=group" json:"group,omitempty"`
@@ -1433,7 +1433,7 @@ func (m *ApplicationResourceResponse) GetManifest() string {
type ApplicationPodLogsQuery struct {
Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
Namespace *string `protobuf:"bytes,2,req,name=namespace" json:"namespace,omitempty"`
Namespace *string `protobuf:"bytes,2,opt,name=namespace" json:"namespace,omitempty"`
PodName *string `protobuf:"bytes,3,opt,name=podName" json:"podName,omitempty"`
Container *string `protobuf:"bytes,4,opt,name=container" json:"container,omitempty"`
SinceSeconds *int64 `protobuf:"varint,5,opt,name=sinceSeconds" json:"sinceSeconds,omitempty"`
@@ -2100,147 +2100,147 @@ func init() {
}
var fileDescriptor_df6e82b174b5eaec = []byte{
// 2237 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x5a, 0xcd, 0x8f, 0x1c, 0x47,
0x15, 0x57, 0xed, 0xe7, 0xcc, 0x1b, 0x7f, 0x56, 0x62, 0xd3, 0x69, 0xaf, 0x37, 0xab, 0xf2, 0xd7,
0x7a, 0xed, 0x9d, 0xb1, 0x07, 0x0b, 0x39, 0x1b, 0x10, 0x38, 0x89, 0xd9, 0x38, 0xec, 0x6e, 0x4c,
0xaf, 0x8d, 0x51, 0x38, 0x40, 0xa5, 0xbb, 0x66, 0xb6, 0xd9, 0x9e, 0xae, 0x76, 0x77, 0xcf, 0x58,
0x23, 0xe3, 0x4b, 0x10, 0x37, 0x04, 0x12, 0xe4, 0x80, 0x10, 0x42, 0x88, 0x28, 0x12, 0x37, 0xe0,
0x12, 0x21, 0x71, 0x81, 0x0b, 0x1f, 0x12, 0x07, 0x04, 0xff, 0x00, 0x58, 0x9c, 0xb8, 0x70, 0xe5,
0x88, 0xaa, 0xba, 0xba, 0xbb, 0x7a, 0x76, 0xa6, 0x67, 0xcc, 0x4e, 0x14, 0xdf, 0xfa, 0xd5, 0x54,
0xbd, 0xf7, 0xab, 0xf7, 0x59, 0xef, 0x69, 0xe0, 0x7c, 0xc4, 0xc2, 0x1e, 0x0b, 0x1b, 0x34, 0x08,
0x3c, 0xd7, 0xa6, 0xb1, 0xcb, 0x7d, 0xfd, 0xbb, 0x1e, 0x84, 0x3c, 0xe6, 0xb8, 0xa6, 0x2d, 0x99,
0x4b, 0x6d, 0xce, 0xdb, 0x1e, 0x6b, 0xd0, 0xc0, 0x6d, 0x50, 0xdf, 0xe7, 0xb1, 0x5c, 0x8e, 0x92,
0xad, 0x26, 0xd9, 0xbf, 0x19, 0xd5, 0x5d, 0x2e, 0x7f, 0xb5, 0x79, 0xc8, 0x1a, 0xbd, 0xeb, 0x8d,
0x36, 0xf3, 0x59, 0x48, 0x63, 0xe6, 0xa8, 0x3d, 0x37, 0xf2, 0x3d, 0x1d, 0x6a, 0xef, 0xb9, 0x3e,
0x0b, 0xfb, 0x8d, 0x60, 0xbf, 0x2d, 0x16, 0xa2, 0x46, 0x87, 0xc5, 0x74, 0xd8, 0xa9, 0xad, 0xb6,
0x1b, 0xef, 0x75, 0xdf, 0xad, 0xdb, 0xbc, 0xd3, 0xa0, 0x61, 0x9b, 0x07, 0x21, 0xff, 0xa6, 0xfc,
0x58, 0xb7, 0x9d, 0x46, 0xaf, 0x99, 0x33, 0xd0, 0xef, 0xd2, 0xbb, 0x4e, 0xbd, 0x60, 0x8f, 0x1e,
0xe4, 0x76, 0x7b, 0x0c, 0xb7, 0x90, 0x05, 0x5c, 0xe9, 0x46, 0x7e, 0xba, 0x31, 0x0f, 0xfb, 0xda,
0x67, 0xc2, 0x86, 0x7c, 0x84, 0xe0, 0xc4, 0xad, 0x5c, 0xde, 0x97, 0xbb, 0x2c, 0xec, 0x63, 0x0c,
0x73, 0x3e, 0xed, 0x30, 0x03, 0xad, 0xa0, 0xd5, 0xaa, 0x25, 0xbf, 0xb1, 0x01, 0x8b, 0x21, 0x6b,
0x85, 0x2c, 0xda, 0x33, 0x66, 0xe4, 0x72, 0x4a, 0x62, 0x13, 0x2a, 0x42, 0x38, 0xb3, 0xe3, 0xc8,
0x98, 0x5d, 0x99, 0x5d, 0xad, 0x5a, 0x19, 0x8d, 0x57, 0xe1, 0x78, 0xc8, 0x22, 0xde, 0x0d, 0x6d,
0xf6, 0x15, 0x16, 0x46, 0x2e, 0xf7, 0x8d, 0x39, 0x79, 0x7a, 0x70, 0x59, 0x70, 0x89, 0x98, 0xc7,
0xec, 0x98, 0x87, 0xc6, 0xbc, 0xdc, 0x92, 0xd1, 0x02, 0x8f, 0x00, 0x6e, 0x2c, 0x24, 0x78, 0xc4,
0x37, 0x79, 0x19, 0xaa, 0x3b, 0xdc, 0x61, 0x23, 0x01, 0x93, 0x4d, 0x38, 0x65, 0xb1, 0x9e, 0x2b,
0x98, 0x6f, 0xb3, 0x98, 0x3a, 0x34, 0xa6, 0x83, 0x9b, 0x67, 0xb2, 0xdb, 0x99, 0x50, 0x09, 0xd5,
0x66, 0x63, 0x46, 0xae, 0x67, 0x34, 0xf9, 0x05, 0x82, 0x65, 0x4d, 0x45, 0x96, 0x02, 0x7e, 0xbb,
0xc7, 0xfc, 0x38, 0x1a, 0xcd, 0xf2, 0x2a, 0x9c, 0x4c, 0xef, 0xb8, 0x43, 0x3b, 0x2c, 0x0a, 0xa8,
0xcd, 0x14, 0xef, 0x83, 0x3f, 0x60, 0x02, 0x47, 0xf4, 0x45, 0x63, 0x56, 0x6e, 0x2c, 0xac, 0xe1,
0x15, 0xa8, 0xa5, 0xf4, 0xfd, 0x3b, 0x6f, 0x18, 0x73, 0x72, 0x8b, 0xbe, 0x44, 0xde, 0x02, 0x43,
0x43, 0xba, 0x4d, 0x7d, 0xb7, 0xc5, 0xa2, 0x78, 0xd2, 0x6b, 0xa3, 0xc2, 0xb5, 0x4f, 0xc1, 0x0b,
0xc5, 0x5b, 0x07, 0xdc, 0x8f, 0x18, 0xf9, 0x2d, 0x2a, 0xc8, 0x78, 0x3d, 0x64, 0x34, 0x66, 0x16,
0x7b, 0xd8, 0x65, 0x51, 0x8c, 0xf7, 0x41, 0x8f, 0x34, 0x29, 0xaa, 0xd6, 0xbc, 0x53, 0xcf, 0x5d,
0xb5, 0x9e, 0xba, 0xaa, 0xfc, 0xf8, 0xba, 0xed, 0xd4, 0x7b, 0xcd, 0x7a, 0xb0, 0xdf, 0xae, 0x0b,
0xc7, 0xaf, 0xeb, 0x81, 0x9b, 0x3a, 0x7e, 0x5d, 0x07, 0xa1, 0x73, 0xc7, 0xa7, 0x61, 0xa1, 0x1b,
0x44, 0x2c, 0x8c, 0x25, 0xf4, 0x8a, 0xa5, 0x28, 0x71, 0xa9, 0x1e, 0xf5, 0x5c, 0x87, 0xc6, 0x42,
0x8d, 0xe2, 0x97, 0x8c, 0x26, 0x1f, 0x14, 0xd1, 0xdf, 0x0f, 0x9c, 0x4f, 0x0a, 0xbd, 0x8e, 0x72,
0x66, 0x00, 0x65, 0xaf, 0x00, 0xf2, 0x0d, 0xe6, 0xb1, 0x1c, 0xe4, 0x30, 0x33, 0x1a, 0xb0, 0x68,
0xd3, 0xc8, 0xa6, 0x4e, 0xca, 0x2a, 0x25, 0x85, 0x13, 0x06, 0x21, 0x0f, 0x68, 0x5b, 0x72, 0xba,
0xcb, 0x3d, 0xd7, 0xee, 0x4b, 0xa5, 0x54, 0xad, 0x83, 0x3f, 0x90, 0x73, 0x50, 0xdb, 0xed, 0xfb,
0xf6, 0xdb, 0x81, 0x4c, 0x88, 0xf8, 0x45, 0x98, 0x77, 0x63, 0xd6, 0x89, 0x0c, 0x24, 0xa3, 0x3a,
0x21, 0xc8, 0x7f, 0xe7, 0xe0, 0xb4, 0x86, 0x4e, 0x1c, 0x28, 0xc3, 0x56, 0xe2, 0x62, 0xc2, 0x82,
0x4e, 0xd8, 0xb7, 0xba, 0xbe, 0xb2, 0x93, 0xa2, 0x84, 0xe0, 0x20, 0xec, 0xfa, 0x4c, 0xe6, 0x8a,
0x8a, 0x95, 0x10, 0xb8, 0x05, 0x95, 0x28, 0x16, 0x29, 0xb0, 0xdd, 0x97, 0x19, 0xa2, 0xd6, 0x7c,
0xeb, 0x70, 0xb6, 0x11, 0xd0, 0x77, 0x15, 0x47, 0x2b, 0xe3, 0x8d, 0x1f, 0x42, 0x35, 0x8d, 0xa9,
0xc8, 0x58, 0x5c, 0x99, 0x5d, 0xad, 0x35, 0x77, 0x0f, 0x2f, 0xe8, 0xed, 0x40, 0xa4, 0x6f, 0x2d,
0x7f, 0x58, 0xb9, 0x14, 0xbc, 0x04, 0xd5, 0x8e, 0x0a, 0xd6, 0xc8, 0xa8, 0x48, 0x6d, 0xe7, 0x0b,
0xf8, 0xab, 0x30, 0xef, 0xfa, 0x2d, 0x1e, 0x19, 0x55, 0x09, 0xe6, 0xb5, 0xc3, 0x81, 0xb9, 0xe3,
0xb7, 0xb8, 0x95, 0x30, 0xc4, 0x0f, 0xe1, 0x68, 0xc8, 0xe2, 0xb0, 0x9f, 0x6a, 0xc1, 0x00, 0xa9,
0xd7, 0x2f, 0x1d, 0x4e, 0x82, 0xa5, 0xb3, 0xb4, 0x8a, 0x12, 0xf0, 0x06, 0xd4, 0xa2, 0xdc, 0xc7,
0x8c, 0x9a, 0x14, 0x68, 0x14, 0x18, 0x69, 0x3e, 0x68, 0xe9, 0x9b, 0xc9, 0xaf, 0x11, 0x2c, 0x1d,
0x88, 0xde, 0xdd, 0x80, 0x95, 0x3a, 0x20, 0x85, 0xb9, 0x28, 0x60, 0xb6, 0x4c, 0xbd, 0xb5, 0xe6,
0xf6, 0xd4, 0xc2, 0x59, 0xca, 0x95, 0xac, 0x4b, 0x33, 0x0e, 0x85, 0x4f, 0x69, 0x87, 0xee, 0xd2,
0xd8, 0xde, 0x2b, 0x43, 0x2b, 0x5c, 0x5f, 0xec, 0x51, 0x95, 0x22, 0x21, 0x84, 0x7f, 0xc8, 0x8f,
0x7b, 0xfd, 0x20, 0x2d, 0x0d, 0xf9, 0x02, 0xf1, 0xc1, 0xd4, 0xd3, 0x0c, 0xf7, 0xbc, 0x77, 0xa9,
0xbd, 0x5f, 0x26, 0xe5, 0x18, 0xcc, 0xb8, 0x8e, 0x14, 0x31, 0x6b, 0xcd, 0xb8, 0xce, 0xb3, 0x05,
0xa2, 0x78, 0x33, 0x98, 0x43, 0x0a, 0x62, 0x99, 0xc0, 0x25, 0xa8, 0xfa, 0x03, 0x45, 0x30, 0x5f,
0x98, 0xa8, 0xf8, 0x19, 0xb0, 0xd8, 0xcb, 0x5e, 0x10, 0xe2, 0xe7, 0x94, 0x14, 0x20, 0xdb, 0x21,
0xef, 0x06, 0xea, 0xd9, 0x90, 0x10, 0x02, 0xc5, 0xbe, 0xeb, 0x3b, 0xc6, 0x42, 0x82, 0x42, 0x7c,
0x93, 0xff, 0x20, 0x78, 0x79, 0x08, 0xf0, 0xb1, 0x46, 0x79, 0x2e, 0xd0, 0xe7, 0xae, 0xb1, 0x38,
0xd2, 0x35, 0x2a, 0x83, 0xae, 0xf1, 0x6f, 0x04, 0x2b, 0x43, 0x6e, 0x3c, 0xbe, 0xa4, 0x3c, 0x37,
0x57, 0x6e, 0xf1, 0xd0, 0x66, 0xc6, 0x62, 0xe2, 0x7f, 0x92, 0x10, 0xde, 0xca, 0xc3, 0x60, 0x8f,
0xfa, 0x46, 0x25, 0xf1, 0xd6, 0x84, 0x22, 0x7f, 0x41, 0x60, 0xa4, 0x37, 0xbc, 0x65, 0xcb, 0xfb,
0x76, 0xfd, 0xe7, 0xff, 0x92, 0xa7, 0x61, 0x81, 0x4a, 0xb4, 0xca, 0xb0, 0x8a, 0x22, 0xdf, 0x41,
0x70, 0xa6, 0x78, 0x9d, 0x68, 0xcb, 0x8d, 0xe2, 0xf4, 0x25, 0x86, 0x5b, 0xb0, 0x98, 0xec, 0x4c,
0x0a, 0x74, 0xad, 0xb9, 0x75, 0xd8, 0xb4, 0x5d, 0x50, 0x5d, 0xca, 0x9c, 0xbc, 0x02, 0x67, 0x86,
0x46, 0xbb, 0x82, 0x61, 0x42, 0x25, 0x2d, 0x55, 0x4a, 0xb9, 0x19, 0x4d, 0xfe, 0x38, 0x5b, 0xcc,
0x7e, 0xdc, 0xd9, 0xe2, 0xed, 0x92, 0x37, 0x73, 0xb9, 0x41, 0x0c, 0x58, 0x0c, 0xb8, 0xa3, 0x6c,
0x21, 0x5b, 0x10, 0x45, 0x8a, 0x73, 0x36, 0xf7, 0x63, 0x2a, 0x3a, 0x31, 0xd5, 0x60, 0xe4, 0x0b,
0xc2, 0x90, 0x91, 0xeb, 0xdb, 0x6c, 0x97, 0xd9, 0xdc, 0x77, 0x22, 0x69, 0x91, 0x59, 0xab, 0xb0,
0x86, 0xdf, 0x84, 0xaa, 0xa4, 0xef, 0xb9, 0x1d, 0x26, 0xfb, 0x8c, 0x5a, 0x73, 0xad, 0x9e, 0xb4,
0x79, 0x75, 0xbd, 0xcd, 0xcb, 0x75, 0x28, 0xda, 0xbc, 0x7a, 0xef, 0x7a, 0x5d, 0x9c, 0xb0, 0xf2,
0xc3, 0x02, 0x4b, 0x4c, 0x5d, 0x6f, 0xcb, 0xf5, 0xe5, 0xf3, 0x41, 0x88, 0xca, 0x17, 0x84, 0xb1,
0x5b, 0xdc, 0xf3, 0xf8, 0xa3, 0xd4, 0x77, 0x13, 0x4a, 0x9c, 0xea, 0xfa, 0xb1, 0xeb, 0x49, 0xf9,
0xd5, 0xe4, 0x06, 0xd9, 0x82, 0x3c, 0xe5, 0x7a, 0x31, 0x0b, 0x65, 0x81, 0xae, 0x5a, 0x8a, 0xca,
0xdc, 0xa9, 0x96, 0xf4, 0x3d, 0x69, 0xcc, 0x24, 0x8e, 0x77, 0x44, 0x77, 0xbc, 0x41, 0x67, 0x3e,
0x2a, 0x7f, 0x2c, 0x3a, 0xb3, 0x6c, 0xe4, 0x58, 0xcf, 0xe5, 0xdd, 0xc8, 0x38, 0x96, 0x94, 0xb1,
0x94, 0x26, 0xbf, 0x43, 0x50, 0xd9, 0xe2, 0xed, 0xdb, 0x7e, 0x1c, 0xf6, 0xe5, 0x7b, 0x93, 0xfb,
0x31, 0xf3, 0x53, 0x8b, 0xa7, 0xa4, 0x50, 0x63, 0xec, 0x76, 0xd8, 0x6e, 0x4c, 0x3b, 0x81, 0xaa,
0xb8, 0xcf, 0xa4, 0xc6, 0xec, 0xb0, 0xb8, 0x9a, 0x47, 0xa3, 0x58, 0x46, 0x5d, 0xc5, 0x92, 0xdf,
0xe2, 0x12, 0xd9, 0x86, 0xdd, 0x38, 0x54, 0x21, 0x57, 0x58, 0xd3, 0x9d, 0x64, 0x3e, 0xc1, 0xa6,
0x48, 0xd2, 0x80, 0x97, 0xb2, 0x47, 0xd8, 0x3d, 0x16, 0x76, 0x5c, 0x9f, 0x96, 0xe6, 0x40, 0x72,
0xbd, 0xe0, 0xf8, 0xe2, 0x55, 0xf2, 0xc0, 0xf5, 0x1d, 0xfe, 0x68, 0xb4, 0x03, 0x93, 0xbf, 0x15,
0x7b, 0x45, 0xed, 0x4c, 0x16, 0x2f, 0x6f, 0xc2, 0x51, 0x11, 0x59, 0x3d, 0xa6, 0x7e, 0x50, 0xc1,
0x4b, 0x0a, 0x41, 0x39, 0x94, 0x87, 0x55, 0x3c, 0x88, 0xb7, 0xe0, 0x38, 0x8d, 0x22, 0xb7, 0xed,
0x33, 0x27, 0xe5, 0x35, 0x33, 0x31, 0xaf, 0xc1, 0xa3, 0x49, 0x13, 0x21, 0x77, 0x28, 0x9d, 0xa7,
0x24, 0xf9, 0x36, 0x82, 0x53, 0x43, 0x99, 0x64, 0xfe, 0x87, 0xb4, 0x74, 0x26, 0x1a, 0x79, 0x7b,
0x8f, 0x39, 0x5d, 0x2f, 0x0d, 0xe1, 0x8c, 0x16, 0xbf, 0x39, 0xdd, 0xc4, 0x02, 0x2a, 0x9d, 0x66,
0x34, 0x5e, 0x06, 0xe8, 0x50, 0xbf, 0x4b, 0x3d, 0x09, 0x61, 0x4e, 0x42, 0xd0, 0x56, 0xc8, 0x12,
0x98, 0xc3, 0xcc, 0xa7, 0xda, 0xd2, 0x5f, 0x21, 0x38, 0x96, 0xa6, 0x26, 0x65, 0x9f, 0x55, 0x38,
0xae, 0xa9, 0x61, 0x27, 0x37, 0xd5, 0xe0, 0xf2, 0x60, 0xda, 0x41, 0xc5, 0xb4, 0x93, 0xda, 0x79,
0xb6, 0x38, 0x0d, 0xe9, 0x15, 0xe6, 0x19, 0x13, 0xe7, 0xfd, 0x2c, 0x50, 0xc9, 0xb7, 0xc0, 0xd8,
0xa6, 0x3e, 0x6d, 0x33, 0x27, 0x03, 0x9e, 0x39, 0xc9, 0x37, 0xf4, 0xd6, 0xeb, 0xd0, 0x8d, 0x4e,
0x56, 0xf6, 0xdd, 0x56, 0x4b, 0xb5, 0x71, 0xcd, 0x7f, 0x2e, 0x03, 0xd6, 0x8d, 0xca, 0xc2, 0x9e,
0x6b, 0x33, 0xfc, 0x03, 0x04, 0x73, 0xa2, 0xca, 0xe0, 0xb3, 0xa3, 0x7c, 0x48, 0x2a, 0xd7, 0x9c,
0xde, 0x3b, 0x5a, 0x48, 0x23, 0x4b, 0xef, 0xfd, 0xfd, 0x5f, 0x3f, 0x9c, 0x39, 0x8d, 0x5f, 0x94,
0x73, 0xb7, 0xde, 0x75, 0x7d, 0x06, 0x16, 0xe1, 0xef, 0x22, 0xc0, 0xaa, 0xf4, 0x69, 0xa3, 0x17,
0x7c, 0x65, 0x14, 0xc4, 0x21, 0x23, 0x1a, 0xf3, 0xac, 0x96, 0x86, 0xea, 0x36, 0x0f, 0x99, 0x48,
0x3a, 0x72, 0x83, 0x04, 0xb0, 0x26, 0x01, 0x9c, 0xc7, 0x64, 0x18, 0x80, 0xc6, 0x63, 0x61, 0xf4,
0x27, 0x0d, 0x96, 0xc8, 0xfd, 0x39, 0x82, 0xf9, 0x07, 0xf2, 0xf1, 0x35, 0x46, 0x49, 0xbb, 0x53,
0x53, 0x92, 0x14, 0x27, 0xd1, 0x92, 0x73, 0x12, 0xe9, 0x59, 0x7c, 0x26, 0x45, 0x1a, 0xc5, 0x21,
0xa3, 0x9d, 0x02, 0xe0, 0x6b, 0x08, 0x7f, 0x88, 0x60, 0x21, 0x19, 0xce, 0xe0, 0x0b, 0xa3, 0x50,
0x16, 0x86, 0x37, 0xe6, 0xf4, 0x26, 0x1d, 0xe4, 0xb2, 0xc4, 0x78, 0x8e, 0x0c, 0x35, 0xe7, 0x46,
0x61, 0x0e, 0xf2, 0x3e, 0x82, 0xd9, 0x4d, 0x36, 0xd6, 0xdf, 0xa6, 0x08, 0xee, 0x80, 0x02, 0x87,
0x98, 0x1a, 0x7f, 0x80, 0xe0, 0xa5, 0x4d, 0x16, 0x0f, 0xcf, 0xe5, 0x78, 0x75, 0x7c, 0x82, 0x55,
0x6e, 0x77, 0x65, 0x82, 0x9d, 0x59, 0x12, 0x6b, 0x48, 0x64, 0x97, 0xf1, 0xa5, 0x32, 0x27, 0x14,
0x0d, 0xf1, 0x23, 0x85, 0xe3, 0xcf, 0x08, 0x4e, 0x0c, 0x0e, 0x39, 0x71, 0x31, 0xfb, 0x0f, 0x9d,
0x81, 0x9a, 0x3b, 0x87, 0x4d, 0x28, 0x45, 0xa6, 0xe4, 0x96, 0x44, 0xfe, 0x2a, 0x7e, 0xa5, 0x0c,
0x79, 0x3a, 0xf7, 0x89, 0x1a, 0x8f, 0xd3, 0xcf, 0x27, 0x72, 0x5a, 0x2e, 0x61, 0xbf, 0x87, 0xe0,
0xc8, 0x26, 0x8b, 0xb7, 0xb3, 0xb1, 0xc7, 0x48, 0xb7, 0x2d, 0xcc, 0x35, 0xcd, 0xa5, 0xba, 0x36,
0xd4, 0x4e, 0x7f, 0xca, 0x54, 0xba, 0x2e, 0x81, 0x5d, 0xc2, 0x17, 0xca, 0x80, 0xe5, 0xa3, 0x96,
0xdf, 0x23, 0x58, 0x48, 0xc6, 0x0a, 0xa3, 0xc5, 0x17, 0x86, 0x86, 0xd3, 0x74, 0xcc, 0xdb, 0x12,
0xeb, 0xe7, 0xcd, 0x6b, 0xc3, 0xb1, 0xea, 0xe7, 0x53, 0xad, 0xd5, 0xe5, 0x05, 0x8a, 0x11, 0xf5,
0x11, 0x02, 0xc8, 0x47, 0x23, 0xf8, 0x72, 0xf9, 0x3d, 0xb4, 0xf1, 0x89, 0x39, 0xdd, 0xe1, 0x08,
0xa9, 0xcb, 0xfb, 0xac, 0x9a, 0x2b, 0xa5, 0xee, 0x1c, 0x30, 0x7b, 0x23, 0x19, 0xa3, 0xfc, 0x0c,
0xc1, 0xbc, 0xec, 0xc5, 0xf1, 0xf9, 0x51, 0x98, 0xf5, 0x56, 0x7d, 0x9a, 0xaa, 0xbf, 0x28, 0xa1,
0xae, 0x34, 0xcb, 0x72, 0xc2, 0x06, 0x5a, 0xc3, 0x3d, 0x58, 0x48, 0x7a, 0xe7, 0xd1, 0xee, 0x51,
0xe8, 0xad, 0xcd, 0x95, 0x92, 0x1a, 0x95, 0x78, 0xa8, 0x4a, 0x47, 0x6b, 0xe3, 0xd2, 0xd1, 0x9c,
0xc8, 0x18, 0xf8, 0x5c, 0x59, 0x3e, 0xf9, 0x18, 0x14, 0x73, 0x45, 0xa2, 0xbb, 0x40, 0x56, 0xc6,
0xa5, 0x24, 0xa1, 0x9d, 0x1f, 0x21, 0x38, 0x31, 0xf8, 0xa4, 0xc1, 0x67, 0x06, 0xd2, 0x91, 0xfe,
0x46, 0x33, 0x8b, 0x5a, 0x1c, 0xf5, 0x1c, 0x22, 0x5f, 0x90, 0x28, 0x36, 0xf0, 0xcd, 0xb1, 0x91,
0xb1, 0x93, 0x06, 0xb4, 0x60, 0xb4, 0x9e, 0x4f, 0x58, 0x7f, 0x83, 0xe0, 0x48, 0xca, 0xf7, 0x5e,
0xc8, 0x58, 0x39, 0xac, 0xe9, 0x05, 0x82, 0x90, 0x45, 0x3e, 0x2b, 0xe1, 0x7f, 0x06, 0xdf, 0x98,
0x10, 0x7e, 0x0a, 0x7b, 0x3d, 0x16, 0x48, 0xff, 0x80, 0xe0, 0xe4, 0x83, 0xc4, 0xef, 0x3f, 0x21,
0xfc, 0xaf, 0x4b, 0xfc, 0x9f, 0xc3, 0xaf, 0x96, 0x3c, 0x39, 0xc6, 0x5d, 0xe3, 0x1a, 0xc2, 0xbf,
0x44, 0x50, 0x49, 0xa7, 0x93, 0xf8, 0xd2, 0xc8, 0xc0, 0x28, 0xce, 0x2f, 0xa7, 0xe9, 0xcc, 0xaa,
0xbe, 0x92, 0xf3, 0xa5, 0x55, 0x4a, 0xc9, 0x17, 0x0e, 0xfd, 0x3e, 0x02, 0x9c, 0xf5, 0x1a, 0x59,
0xf7, 0x81, 0x2f, 0x16, 0x44, 0x8d, 0x6c, 0x2a, 0xcd, 0x4b, 0x63, 0xf7, 0x15, 0xab, 0xd4, 0x5a,
0x69, 0x95, 0xe2, 0x99, 0xfc, 0xef, 0x21, 0xa8, 0x6d, 0xb2, 0xec, 0x39, 0x5c, 0xa2, 0xcb, 0xe2,
0x68, 0xd6, 0x5c, 0x1d, 0xbf, 0x51, 0x21, 0xba, 0x2a, 0x11, 0x5d, 0xc4, 0xe5, 0xaa, 0x4a, 0x01,
0xfc, 0x04, 0xc1, 0xd1, 0xbb, 0xba, 0x8b, 0xe2, 0xab, 0xe3, 0x24, 0x15, 0x32, 0xf9, 0xe4, 0xb8,
0x3e, 0x2d, 0x71, 0xad, 0x93, 0x89, 0x70, 0x6d, 0xa8, 0x19, 0xe9, 0x4f, 0x11, 0xbc, 0xa0, 0xf7,
0x0f, 0x6a, 0x9a, 0xf6, 0xff, 0xea, 0xad, 0x64, 0x28, 0x47, 0x6e, 0x48, 0x7c, 0x75, 0x7c, 0x75,
0x12, 0x7c, 0x0d, 0x35, 0x62, 0xc3, 0x3f, 0x46, 0x70, 0x52, 0xce, 0x2a, 0x75, 0xc6, 0x03, 0x25,
0x66, 0xd4, 0x64, 0x73, 0x82, 0x12, 0xa3, 0xf2, 0x0f, 0x79, 0x26, 0x50, 0x1b, 0x6a, 0x0e, 0x89,
0xbf, 0x8f, 0xe0, 0x58, 0x5a, 0xd4, 0x94, 0x75, 0xd7, 0xc7, 0x29, 0xee, 0x59, 0x8b, 0xa0, 0x72,
0xb7, 0xb5, 0xc9, 0xdc, 0xed, 0x43, 0x04, 0x8b, 0x6a, 0x96, 0x58, 0xf2, 0x54, 0xd0, 0x86, 0x8d,
0xe6, 0xa9, 0xc2, 0xae, 0x74, 0x90, 0x45, 0xbe, 0x26, 0xc5, 0xde, 0xc7, 0x8d, 0x32, 0xb1, 0x01,
0x77, 0xa2, 0xc6, 0x63, 0x35, 0x45, 0x7a, 0xd2, 0xf0, 0x78, 0x3b, 0x7a, 0x87, 0xe0, 0xd2, 0x82,
0x28, 0xf6, 0x5c, 0x43, 0xaf, 0x7d, 0xf1, 0x4f, 0x4f, 0x97, 0xd1, 0x5f, 0x9f, 0x2e, 0xa3, 0x7f,
0x3c, 0x5d, 0x46, 0xef, 0xdc, 0x9c, 0xec, 0xff, 0x1f, 0xb6, 0xe7, 0x32, 0x3f, 0xd6, 0xd9, 0xfe,
0x2f, 0x00, 0x00, 0xff, 0xff, 0xef, 0x54, 0xd5, 0x40, 0xe5, 0x22, 0x00, 0x00,
// 2236 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,
}
// Reference imports to suppress errors if they are not otherwise used.
@@ -3455,27 +3455,21 @@ func (m *ApplicationResourceEventsQuery) MarshalToSizedBuffer(dAtA []byte) (int,
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if m.ResourceUID == nil {
return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("resourceUID")
} else {
if m.ResourceUID != nil {
i -= len(*m.ResourceUID)
copy(dAtA[i:], *m.ResourceUID)
i = encodeVarintApplication(dAtA, i, uint64(len(*m.ResourceUID)))
i--
dAtA[i] = 0x22
}
if m.ResourceName == nil {
return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("resourceName")
} else {
if m.ResourceName != nil {
i -= len(*m.ResourceName)
copy(dAtA[i:], *m.ResourceName)
i = encodeVarintApplication(dAtA, i, uint64(len(*m.ResourceName)))
i--
dAtA[i] = 0x1a
}
if m.ResourceNamespace == nil {
return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("resourceNamespace")
} else {
if m.ResourceNamespace != nil {
i -= len(*m.ResourceNamespace)
copy(dAtA[i:], *m.ResourceNamespace)
i = encodeVarintApplication(dAtA, i, uint64(len(*m.ResourceNamespace)))
@@ -4136,9 +4130,7 @@ func (m *ApplicationResourceRequest) MarshalToSizedBuffer(dAtA []byte) (int, err
i--
dAtA[i] = 0x1a
}
if m.Namespace == nil {
return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("namespace")
} else {
if m.Namespace != nil {
i -= len(*m.Namespace)
copy(dAtA[i:], *m.Namespace)
i = encodeVarintApplication(dAtA, i, uint64(len(*m.Namespace)))
@@ -4233,9 +4225,7 @@ func (m *ApplicationResourcePatchRequest) MarshalToSizedBuffer(dAtA []byte) (int
i--
dAtA[i] = 0x1a
}
if m.Namespace == nil {
return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("namespace")
} else {
if m.Namespace != nil {
i -= len(*m.Namespace)
copy(dAtA[i:], *m.Namespace)
i = encodeVarintApplication(dAtA, i, uint64(len(*m.Namespace)))
@@ -4332,9 +4322,7 @@ func (m *ApplicationResourceDeleteRequest) MarshalToSizedBuffer(dAtA []byte) (in
i--
dAtA[i] = 0x1a
}
if m.Namespace == nil {
return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("namespace")
} else {
if m.Namespace != nil {
i -= len(*m.Namespace)
copy(dAtA[i:], *m.Namespace)
i = encodeVarintApplication(dAtA, i, uint64(len(*m.Namespace)))
@@ -4420,9 +4408,7 @@ func (m *ResourceActionRunRequest) MarshalToSizedBuffer(dAtA []byte) (int, error
i--
dAtA[i] = 0x1a
}
if m.Namespace == nil {
return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("namespace")
} else {
if m.Namespace != nil {
i -= len(*m.Namespace)
copy(dAtA[i:], *m.Namespace)
i = encodeVarintApplication(dAtA, i, uint64(len(*m.Namespace)))
@@ -4633,9 +4619,7 @@ func (m *ApplicationPodLogsQuery) MarshalToSizedBuffer(dAtA []byte) (int, error)
i--
dAtA[i] = 0x1a
}
if m.Namespace == nil {
return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("namespace")
} else {
if m.Namespace != nil {
i -= len(*m.Namespace)
copy(dAtA[i:], *m.Namespace)
i = encodeVarintApplication(dAtA, i, uint64(len(*m.Namespace)))
@@ -6437,7 +6421,6 @@ func (m *ApplicationResourceEventsQuery) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.ResourceNamespace = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000002)
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ResourceName", wireType)
@@ -6471,7 +6454,6 @@ func (m *ApplicationResourceEventsQuery) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.ResourceName = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000004)
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ResourceUID", wireType)
@@ -6505,7 +6487,6 @@ func (m *ApplicationResourceEventsQuery) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.ResourceUID = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000008)
default:
iNdEx = preIndex
skippy, err := skipApplication(dAtA[iNdEx:])
@@ -6525,15 +6506,6 @@ func (m *ApplicationResourceEventsQuery) Unmarshal(dAtA []byte) error {
if hasFields[0]&uint64(0x00000001) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("name")
}
if hasFields[0]&uint64(0x00000002) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("resourceNamespace")
}
if hasFields[0]&uint64(0x00000004) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("resourceName")
}
if hasFields[0]&uint64(0x00000008) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("resourceUID")
}
if iNdEx > l {
return io.ErrUnexpectedEOF
@@ -8123,7 +8095,6 @@ func (m *ApplicationResourceRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.Namespace = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000002)
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ResourceName", wireType)
@@ -8157,7 +8128,7 @@ func (m *ApplicationResourceRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.ResourceName = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000004)
hasFields[0] |= uint64(0x00000002)
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType)
@@ -8191,7 +8162,7 @@ func (m *ApplicationResourceRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.Version = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000008)
hasFields[0] |= uint64(0x00000004)
case 5:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Group", wireType)
@@ -8258,7 +8229,7 @@ func (m *ApplicationResourceRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.Kind = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000010)
hasFields[0] |= uint64(0x00000008)
default:
iNdEx = preIndex
skippy, err := skipApplication(dAtA[iNdEx:])
@@ -8279,15 +8250,12 @@ func (m *ApplicationResourceRequest) Unmarshal(dAtA []byte) error {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("name")
}
if hasFields[0]&uint64(0x00000002) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("namespace")
}
if hasFields[0]&uint64(0x00000004) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("resourceName")
}
if hasFields[0]&uint64(0x00000008) == 0 {
if hasFields[0]&uint64(0x00000004) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("version")
}
if hasFields[0]&uint64(0x00000010) == 0 {
if hasFields[0]&uint64(0x00000008) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("kind")
}
@@ -8393,7 +8361,6 @@ func (m *ApplicationResourcePatchRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.Namespace = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000002)
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ResourceName", wireType)
@@ -8427,7 +8394,7 @@ func (m *ApplicationResourcePatchRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.ResourceName = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000004)
hasFields[0] |= uint64(0x00000002)
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType)
@@ -8461,7 +8428,7 @@ func (m *ApplicationResourcePatchRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.Version = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000008)
hasFields[0] |= uint64(0x00000004)
case 5:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Group", wireType)
@@ -8528,7 +8495,7 @@ func (m *ApplicationResourcePatchRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.Kind = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000010)
hasFields[0] |= uint64(0x00000008)
case 7:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Patch", wireType)
@@ -8562,7 +8529,7 @@ func (m *ApplicationResourcePatchRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.Patch = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000020)
hasFields[0] |= uint64(0x00000010)
case 8:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field PatchType", wireType)
@@ -8596,7 +8563,7 @@ func (m *ApplicationResourcePatchRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.PatchType = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000040)
hasFields[0] |= uint64(0x00000020)
default:
iNdEx = preIndex
skippy, err := skipApplication(dAtA[iNdEx:])
@@ -8617,21 +8584,18 @@ func (m *ApplicationResourcePatchRequest) Unmarshal(dAtA []byte) error {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("name")
}
if hasFields[0]&uint64(0x00000002) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("namespace")
}
if hasFields[0]&uint64(0x00000004) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("resourceName")
}
if hasFields[0]&uint64(0x00000008) == 0 {
if hasFields[0]&uint64(0x00000004) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("version")
}
if hasFields[0]&uint64(0x00000010) == 0 {
if hasFields[0]&uint64(0x00000008) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("kind")
}
if hasFields[0]&uint64(0x00000020) == 0 {
if hasFields[0]&uint64(0x00000010) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("patch")
}
if hasFields[0]&uint64(0x00000040) == 0 {
if hasFields[0]&uint64(0x00000020) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("patchType")
}
@@ -8737,7 +8701,6 @@ func (m *ApplicationResourceDeleteRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.Namespace = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000002)
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ResourceName", wireType)
@@ -8771,7 +8734,7 @@ func (m *ApplicationResourceDeleteRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.ResourceName = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000004)
hasFields[0] |= uint64(0x00000002)
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType)
@@ -8805,7 +8768,7 @@ func (m *ApplicationResourceDeleteRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.Version = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000008)
hasFields[0] |= uint64(0x00000004)
case 5:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Group", wireType)
@@ -8872,7 +8835,7 @@ func (m *ApplicationResourceDeleteRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.Kind = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000010)
hasFields[0] |= uint64(0x00000008)
case 7:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Force", wireType)
@@ -8935,15 +8898,12 @@ func (m *ApplicationResourceDeleteRequest) Unmarshal(dAtA []byte) error {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("name")
}
if hasFields[0]&uint64(0x00000002) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("namespace")
}
if hasFields[0]&uint64(0x00000004) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("resourceName")
}
if hasFields[0]&uint64(0x00000008) == 0 {
if hasFields[0]&uint64(0x00000004) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("version")
}
if hasFields[0]&uint64(0x00000010) == 0 {
if hasFields[0]&uint64(0x00000008) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("kind")
}
@@ -9049,7 +9009,6 @@ func (m *ResourceActionRunRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.Namespace = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000002)
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ResourceName", wireType)
@@ -9083,7 +9042,7 @@ func (m *ResourceActionRunRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.ResourceName = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000004)
hasFields[0] |= uint64(0x00000002)
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType)
@@ -9117,7 +9076,7 @@ func (m *ResourceActionRunRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.Version = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000008)
hasFields[0] |= uint64(0x00000004)
case 5:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Group", wireType)
@@ -9184,7 +9143,7 @@ func (m *ResourceActionRunRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.Kind = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000010)
hasFields[0] |= uint64(0x00000008)
case 7:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Action", wireType)
@@ -9218,7 +9177,7 @@ func (m *ResourceActionRunRequest) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.Action = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000020)
hasFields[0] |= uint64(0x00000010)
default:
iNdEx = preIndex
skippy, err := skipApplication(dAtA[iNdEx:])
@@ -9239,18 +9198,15 @@ func (m *ResourceActionRunRequest) Unmarshal(dAtA []byte) error {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("name")
}
if hasFields[0]&uint64(0x00000002) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("namespace")
}
if hasFields[0]&uint64(0x00000004) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("resourceName")
}
if hasFields[0]&uint64(0x00000008) == 0 {
if hasFields[0]&uint64(0x00000004) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("version")
}
if hasFields[0]&uint64(0x00000010) == 0 {
if hasFields[0]&uint64(0x00000008) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("kind")
}
if hasFields[0]&uint64(0x00000020) == 0 {
if hasFields[0]&uint64(0x00000010) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("action")
}
@@ -9530,7 +9486,6 @@ func (m *ApplicationPodLogsQuery) Unmarshal(dAtA []byte) error {
s := string(dAtA[iNdEx:postIndex])
m.Namespace = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000002)
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field PodName", wireType)
@@ -9899,9 +9854,6 @@ func (m *ApplicationPodLogsQuery) Unmarshal(dAtA []byte) error {
if hasFields[0]&uint64(0x00000001) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("name")
}
if hasFields[0]&uint64(0x00000002) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("namespace")
}
if iNdEx > l {
return io.ErrUnexpectedEOF

View File

@@ -8,6 +8,7 @@ import (
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
@@ -63,6 +64,8 @@ func NewConnection(address string, timeoutSeconds int, tlsConfig *TLSConfigurati
grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor(retryOpts...)),
grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(unaryInterceptors...)),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxGRPCMessageSize), grpc.MaxCallSendMsgSize(MaxGRPCMessageSize)),
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),
grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()),
}
tlsC := &tls.Config{}

View File

@@ -18,6 +18,8 @@ import (
"strings"
"time"
"github.com/argoproj/argo-cd/v2/util/io/files"
"github.com/Masterminds/semver/v3"
"github.com/TomOnTime/utfutil"
"github.com/argoproj/gitops-engine/pkg/utils/kube"
@@ -905,7 +907,8 @@ func GenerateManifests(ctx context.Context, appPath, repoRoot, revision string,
if directory = q.ApplicationSource.Directory; directory == nil {
directory = &v1alpha1.ApplicationSourceDirectory{}
}
targetObjs, err = findManifests(appPath, repoRoot, env, *directory, q.EnabledSourceTypes)
logCtx := log.WithField("application", q.AppName)
targetObjs, err = findManifests(logCtx, appPath, repoRoot, env, *directory, q.EnabledSourceTypes)
}
if err != nil {
return nil, err
@@ -1075,12 +1078,32 @@ func isNullList(obj *unstructured.Unstructured) bool {
var manifestFile = regexp.MustCompile(`^.*\.(yaml|yml|json|jsonnet)$`)
// findManifests looks at all yaml files in a directory and unmarshals them into a list of unstructured objects
func findManifests(appPath string, repoRoot string, env *v1alpha1.Env, directory v1alpha1.ApplicationSourceDirectory, enabledManifestGeneration map[string]bool) ([]*unstructured.Unstructured, error) {
func findManifests(logCtx *log.Entry, appPath string, repoRoot string, env *v1alpha1.Env, directory v1alpha1.ApplicationSourceDirectory, enabledManifestGeneration map[string]bool) ([]*unstructured.Unstructured, error) {
var objs []*unstructured.Unstructured
err := filepath.Walk(appPath, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
relPath, err := filepath.Rel(appPath, path)
if err != nil {
return fmt.Errorf("failed to get relative path of symlink: %w", err)
}
if files.IsSymlink(f) {
realPath, err := filepath.EvalSymlinks(path)
if err != nil {
logCtx.Debugf("error checking symlink realpath: %s", err)
if os.IsNotExist(err) {
log.Warnf("ignoring out-of-bounds symlink at %q: %s", relPath, err)
return nil
} else {
return fmt.Errorf("failed to evaluate symlink at %q: %w", relPath, err)
}
}
if !files.Inbound(realPath, appPath) {
logCtx.Warnf("illegal filepath in symlink: %s", realPath)
return fmt.Errorf("illegal filepath in symlink at %q", relPath)
}
}
if f.IsDir() {
if path != appPath && !directory.Recurse {
return filepath.SkipDir
@@ -1093,10 +1116,6 @@ func findManifests(appPath string, repoRoot string, env *v1alpha1.Env, directory
return nil
}
relPath, err := filepath.Rel(appPath, path)
if err != nil {
return err
}
if directory.Exclude != "" && glob.Match(directory.Exclude, relPath) {
return nil
}
@@ -1252,7 +1271,7 @@ func runConfigManagementPlugin(appPath, repoRoot string, envVars *v1alpha1.Env,
}
}
env, err := getPluginEnvs(envVars, q, creds)
env, err := getPluginEnvs(envVars, q, creds, false)
if err != nil {
return nil, err
}
@@ -1270,8 +1289,14 @@ func runConfigManagementPlugin(appPath, repoRoot string, envVars *v1alpha1.Env,
return kube.SplitYAML([]byte(out))
}
func getPluginEnvs(envVars *v1alpha1.Env, q *apiclient.ManifestRequest, creds git.Creds) ([]string, error) {
env := append(os.Environ(), envVars.Environ()...)
func getPluginEnvs(envVars *v1alpha1.Env, q *apiclient.ManifestRequest, creds git.Creds, remote bool) ([]string, error) {
env := envVars.Environ()
// Local plugins need also to have access to the local environment variables.
// Remote side car plugins will use the environment in the side car
// container.
if !remote {
env = append(os.Environ(), env...)
}
if creds != nil {
closer, environ, err := creds.Environ()
if err != nil {
@@ -1294,27 +1319,29 @@ func getPluginEnvs(envVars *v1alpha1.Env, q *apiclient.ManifestRequest, creds gi
if q.ApplicationSource.Plugin != nil {
pluginEnv := q.ApplicationSource.Plugin.Env
for i, j := range pluginEnv {
pluginEnv[i].Value = parsedEnv.Envsubst(j.Value)
for _, entry := range pluginEnv {
newValue := parsedEnv.Envsubst(entry.Value)
env = append(env, fmt.Sprintf("ARGOCD_ENV_%s=%s", entry.Name, newValue))
}
env = append(env, pluginEnv.Environ()...)
}
return env, nil
}
func runConfigManagementPluginSidecars(ctx context.Context, appPath, repoPath string, envVars *v1alpha1.Env, q *apiclient.ManifestRequest, creds git.Creds, tarDoneCh chan<- bool) ([]*unstructured.Unstructured, error) {
// compute variables.
env, err := getPluginEnvs(envVars, q, creds, true)
if err != nil {
return nil, err
}
// detect config management plugin server (sidecar)
conn, cmpClient, err := discovery.DetectConfigManagementPlugin(ctx, appPath)
conn, cmpClient, err := discovery.DetectConfigManagementPlugin(ctx, appPath, env)
if err != nil {
return nil, err
}
defer io.Close(conn)
// generate manifests using commands provided in plugin config file in detected cmp-server sidecar
env, err := getPluginEnvs(envVars, q, creds)
if err != nil {
return nil, err
}
cmpManifests, err := generateManifestsCMP(ctx, appPath, repoPath, env, cmpClient, tarDoneCh)
if err != nil {
return nil, fmt.Errorf("error generating manifests in cmp: %s", err)

View File

@@ -1,47 +0,0 @@
//go:build !race
// +build !race
package repository
import (
"os"
"path/filepath"
"sync"
"testing"
"github.com/stretchr/testify/assert"
argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
)
func TestHelmDependencyWithConcurrency(t *testing.T) {
// !race:
// Un-synchronized use of a random source, will be fixed when this is merged:
// https://github.com/argoproj/argo-cd/issues/4728
cleanup := func() {
_ = os.Remove(filepath.Join("../../util/helm/testdata/dependency", helmDepUpMarkerFile))
_ = os.RemoveAll(filepath.Join("../../util/helm/testdata/dependency", "charts"))
}
cleanup()
defer cleanup()
helmRepo := argoappv1.Repository{Name: "bitnami", Type: "helm", Repo: "https://charts.bitnami.com/bitnami"}
var wg sync.WaitGroup
wg.Add(3)
for i := 0; i < 3; i++ {
go func() {
res, err := helmTemplate("../../util/helm/testdata/dependency", "../..", nil, &apiclient.ManifestRequest{
ApplicationSource: &argoappv1.ApplicationSource{},
Repos: []*argoappv1.Repository{&helmRepo},
}, false)
assert.NoError(t, err)
assert.NotNil(t, res)
wg.Done()
}()
}
wg.Wait()
}

View File

@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
log "github.com/sirupsen/logrus"
goio "io"
"io/ioutil"
"os"
@@ -136,7 +137,7 @@ func TestGenerateYamlManifestInDir(t *testing.T) {
q := apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &src}
// update this value if we add/remove manifests
const countOfManifests = 46
const countOfManifests = 47
res1, err := service.GenerateManifest(context.Background(), &q)
@@ -149,6 +150,76 @@ func TestGenerateYamlManifestInDir(t *testing.T) {
assert.Equal(t, 3, len(res2.Manifests))
}
func Test_GenerateManifests_NoOutOfBoundsAccess(t *testing.T) {
testCases := []struct{
name string
outOfBoundsFilename string
outOfBoundsFileContents string
mustNotContain string // Optional string that must not appear in error or manifest output. If empty, use outOfBoundsFileContents.
}{
{
name: "out of bounds JSON file should not appear in error output",
outOfBoundsFilename: "test.json",
outOfBoundsFileContents: `{"some": "json"}`,
},
{
name: "malformed JSON file contents should not appear in error output",
outOfBoundsFilename: "test.json",
outOfBoundsFileContents: "$",
},
{
name: "out of bounds JSON manifest should not appear in manifest output",
outOfBoundsFilename: "test.json",
// JSON marshalling is deterministic. So if there's a leak, exactly this should appear in the manifests.
outOfBoundsFileContents: `{"apiVersion":"v1","kind":"Secret","metadata":{"name":"test","namespace":"default"},"type":"Opaque"}`,
},
{
name: "out of bounds YAML manifest should not appear in manifest output",
outOfBoundsFilename: "test.yaml",
outOfBoundsFileContents: "apiVersion: v1\nkind: Secret\nmetadata:\n name: test\n namespace: default\ntype: Opaque",
mustNotContain: `{"apiVersion":"v1","kind":"Secret","metadata":{"name":"test","namespace":"default"},"type":"Opaque"}`,
},
}
for _, testCase := range testCases {
testCaseCopy := testCase
t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel()
outOfBoundsDir := t.TempDir()
outOfBoundsFile := path.Join(outOfBoundsDir, testCaseCopy.outOfBoundsFilename)
err := os.WriteFile(outOfBoundsFile, []byte(testCaseCopy.outOfBoundsFileContents), os.FileMode(0444))
require.NoError(t, err)
repoDir := t.TempDir()
err = os.Symlink(outOfBoundsFile, path.Join(repoDir, testCaseCopy.outOfBoundsFilename))
require.NoError(t, err)
var mustNotContain = testCaseCopy.outOfBoundsFileContents
if testCaseCopy.mustNotContain != "" {
mustNotContain = testCaseCopy.mustNotContain
}
q := apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &argoappv1.ApplicationSource{}}
res, err := GenerateManifests(context.Background(), repoDir, "", "", &q, false, &git.NoopCredsStore{})
require.Error(t, err)
assert.NotContains(t, err.Error(), mustNotContain)
assert.Contains(t, err.Error(), "illegal filepath")
assert.Nil(t, res)
})
}
}
func TestGenerateManifests_MissingSymlinkDestination(t *testing.T) {
repoDir := t.TempDir()
err := os.Symlink("/obviously/does/not/exist", path.Join(repoDir, "test.yaml"))
require.NoError(t, err)
q := apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &argoappv1.ApplicationSource{}}
_, err = GenerateManifests(context.Background(), repoDir, "", "", &q, false, &git.NoopCredsStore{})
require.NoError(t, err)
}
func TestGenerateManifests_K8SAPIResetCache(t *testing.T) {
service := newService("../..")
@@ -283,28 +354,6 @@ func TestGenerateJsonnetLibOutside(t *testing.T) {
require.Contains(t, err.Error(), "value file '../../../testdata/jsonnet/vendor' resolved to outside repository root")
}
func TestGenerateHelmChartWithDependencies(t *testing.T) {
service := newService("../..")
cleanup := func() {
_ = os.Remove(filepath.Join("../../util/helm/testdata/dependency", helmDepUpMarkerFile))
_ = os.RemoveAll(filepath.Join("../../util/helm/testdata/dependency", "charts"))
}
cleanup()
defer cleanup()
helmRepo := argoappv1.Repository{Name: "bitnami", Type: "helm", Repo: "https://charts.bitnami.com/bitnami"}
q := apiclient.ManifestRequest{
Repo: &argoappv1.Repository{},
ApplicationSource: &argoappv1.ApplicationSource{
Path: "./util/helm/testdata/dependency",
},
Repos: []*argoappv1.Repository{&helmRepo},
}
res1, err := service.GenerateManifest(context.Background(), &q)
assert.Nil(t, err)
assert.Len(t, res1.Manifests, 10)
}
func TestManifestGenErrorCacheByNumRequests(t *testing.T) {
// Returns the state of the manifest generation cache, by querying the cache for the previously set result
@@ -1031,7 +1080,7 @@ func TestRunCustomTool(t *testing.T) {
Name: "test",
Generate: argoappv1.Command{
Command: []string{"sh", "-c"},
Args: []string{`echo "{\"kind\": \"FakeObject\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"GIT_ASKPASS\": \"$GIT_ASKPASS\", \"GIT_USERNAME\": \"$GIT_USERNAME\", \"GIT_PASSWORD\": \"$GIT_PASSWORD\"}, \"labels\": {\"revision\": \"$TEST_REVISION\"}}}"`},
Args: []string{`echo "{\"kind\": \"FakeObject\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"GIT_ASKPASS\": \"$GIT_ASKPASS\", \"GIT_USERNAME\": \"$GIT_USERNAME\", \"GIT_PASSWORD\": \"$GIT_PASSWORD\"}, \"labels\": {\"revision\": \"$ARGOCD_ENV_TEST_REVISION\"}}}"`},
},
}},
Repo: &argoappv1.Repository{
@@ -1080,6 +1129,7 @@ func TestListApps(t *testing.T) {
"kustomization_yml": "Kustomize",
"my-chart": "Helm",
"my-chart-2": "Helm",
"values-files": "Helm",
}
assert.Equal(t, expectedApps, res.Apps)
}
@@ -1599,7 +1649,7 @@ func TestFindResources(t *testing.T) {
for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
objs, err := findManifests("testdata/app-include-exclude", ".", nil, argoappv1.ApplicationSourceDirectory{
objs, err := findManifests(&log.Entry{}, "testdata/app-include-exclude", ".", nil, argoappv1.ApplicationSourceDirectory{
Recurse: true,
Include: tc.include,
Exclude: tc.exclude,
@@ -1617,7 +1667,7 @@ func TestFindResources(t *testing.T) {
}
func TestFindManifests_Exclude(t *testing.T) {
objs, err := findManifests("testdata/app-include-exclude", ".", nil, argoappv1.ApplicationSourceDirectory{
objs, err := findManifests(&log.Entry{}, "testdata/app-include-exclude", ".", nil, argoappv1.ApplicationSourceDirectory{
Recurse: true,
Exclude: "subdir/deploymentSub.yaml",
}, map[string]bool{})
@@ -1630,7 +1680,7 @@ func TestFindManifests_Exclude(t *testing.T) {
}
func TestFindManifests_Exclude_NothingMatches(t *testing.T) {
objs, err := findManifests("testdata/app-include-exclude", ".", nil, argoappv1.ApplicationSourceDirectory{
objs, err := findManifests(&log.Entry{}, "testdata/app-include-exclude", ".", nil, argoappv1.ApplicationSourceDirectory{
Recurse: true,
Exclude: "nothing.yaml",
}, map[string]bool{})
@@ -1825,3 +1875,32 @@ func runGit(t *testing.T, workDir string, args ...string) string {
require.NoError(t, err, stringOut)
return stringOut
}
func Test_findHelmValueFilesInPath(t *testing.T) {
t.Run("does not exist", func(t *testing.T) {
files, err := findHelmValueFilesInPath("/obviously/does/not/exist")
assert.Error(t, err)
assert.Empty(t, files)
})
t.Run("values files", func(t *testing.T) {
files, err := findHelmValueFilesInPath("./testdata/values-files")
assert.NoError(t, err)
assert.Len(t, files, 4)
})
}
func Test_populateHelmAppDetails(t *testing.T) {
res := apiclient.RepoAppDetailsResponse{}
q := apiclient.RepoServerAppDetailsQuery{
Repo: &argoappv1.Repository{},
Source: &argoappv1.ApplicationSource{
Helm: &argoappv1.ApplicationSourceHelm{ValueFiles: []string{"exclude.yaml", "has-the-word-values.yaml"}},
},
}
appPath, err := filepath.Abs("./testdata/values-files/")
require.NoError(t, err)
err = populateHelmAppDetails(&res, appPath, appPath, &q)
require.NoError(t, err)
assert.Len(t, res.Helm.Parameters, 3)
assert.Len(t, res.Helm.ValueFiles, 4)
}

View File

@@ -0,0 +1,2 @@
name: my-chart
version: 1.1.0

View File

@@ -0,0 +1 @@
exclude: yaml

View File

@@ -0,0 +1,4 @@
has:
the:
word:
values: yaml

View File

@@ -0,0 +1 @@
values: yaml

View File

View File

@@ -70,7 +70,7 @@ var (
watchAPIBufferSize = env.ParseNumFromEnv(argocommon.EnvWatchAPIBufferSize, 1000, 0, math.MaxInt32)
)
// Server provides a Application service
// Server provides an Application service
type Server struct {
ns string
kubeclientset kubernetes.Interface
@@ -131,11 +131,11 @@ func NewServer(
func (s *Server) List(ctx context.Context, q *application.ApplicationQuery) (*appv1.ApplicationList, error) {
labelsMap, err := labels.ConvertSelectorToLabelsMap(q.GetSelector())
if err != nil {
return nil, err
return nil, fmt.Errorf("error converting selector to labels map: %w", err)
}
apps, err := s.appLister.List(labelsMap.AsSelector())
if err != nil {
return nil, err
return nil, fmt.Errorf("error listing apps with selectors: %w", err)
}
newItems := make([]appv1.Application, 0)
for _, a := range apps {
@@ -146,7 +146,7 @@ func (s *Server) List(ctx context.Context, q *application.ApplicationQuery) (*ap
if q.Name != nil {
newItems, err = argoutil.FilterByName(newItems, *q.Name)
if err != nil {
return nil, err
return nil, fmt.Errorf("error filtering applications by name: %w", err)
}
}
@@ -189,7 +189,7 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
}
err := s.validateAndNormalizeApp(ctx, a, validate)
if err != nil {
return nil, err
return nil, fmt.Errorf("error while validating and normalizing app: %w", err)
}
created, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Create(ctx, a, metav1.CreateOptions{})
if err == nil {
@@ -198,7 +198,7 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
return created, nil
}
if !apierr.IsAlreadyExists(err) {
return nil, err
return nil, fmt.Errorf("error creating application: %w", err)
}
// act idempotent if existing spec matches new spec
existing, err := s.appLister.Get(a.Name)
@@ -221,7 +221,7 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
}
updated, err := s.updateApp(existing, a, ctx, true)
if err != nil {
return nil, err
return nil, fmt.Errorf("error updating application: %w", err)
}
return updated, nil
}
@@ -238,53 +238,53 @@ func (s *Server) queryRepoServer(ctx context.Context, a *v1alpha1.Application, a
closer, client, err := s.repoClientset.NewRepoServerClient()
if err != nil {
return err
return fmt.Errorf("error creating repo server client: %w", err)
}
defer ioutil.Close(closer)
repo, err := s.db.GetRepository(ctx, a.Spec.Source.RepoURL)
if err != nil {
return err
return fmt.Errorf("error getting repository: %w", err)
}
kustomizeSettings, err := s.settingsMgr.GetKustomizeSettings()
if err != nil {
return err
return fmt.Errorf("error getting kustomize settings: %w", err)
}
kustomizeOptions, err := kustomizeSettings.GetOptions(a.Spec.Source)
if err != nil {
return err
return fmt.Errorf("error getting kustomize settings options: %w", err)
}
proj, err := argo.GetAppProject(&a.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", a.Spec.Project)
}
return err
return fmt.Errorf("error getting application's project: %w", err)
}
helmRepos, err := s.db.ListHelmRepositories(ctx)
if err != nil {
return err
return fmt.Errorf("error listing helm repositories: %w", err)
}
permittedHelmRepos, err := argo.GetPermittedRepos(proj, helmRepos)
if err != nil {
return err
return fmt.Errorf("error retrieving permitted repos: %w", err)
}
helmRepositoryCredentials, err := s.db.GetAllHelmRepositoryCredentials(ctx)
if err != nil {
return err
return fmt.Errorf("error getting helm repository credentials: %w", err)
}
helmOptions, err := s.settingsMgr.GetHelmSettings()
if err != nil {
return err
return fmt.Errorf("error getting helm settings: %w", err)
}
permittedHelmCredentials, err := argo.GetPermittedReposCredentials(proj, helmRepositoryCredentials)
if err != nil {
return err
return fmt.Errorf("error getting permitted repos credentials: %w", err)
}
enabledSourceTypes, err := s.settingsMgr.GetEnabledSourceTypes()
if err != nil {
return err
return fmt.Errorf("error getting settings enabled source types: %w", err)
}
return action(client, repo, permittedHelmRepos, permittedHelmCredentials, helmOptions, kustomizeOptions, enabledSourceTypes)
}
@@ -293,7 +293,7 @@ func (s *Server) queryRepoServer(ctx context.Context, a *v1alpha1.Application, a
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, err
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
@@ -308,26 +308,26 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
}
appInstanceLabelKey, err := s.settingsMgr.GetAppInstanceLabelKey()
if err != nil {
return err
return fmt.Errorf("error getting app instance label key from settings: %w", err)
}
plugins, err := s.plugins()
if err != nil {
return err
return fmt.Errorf("error getting plugins: %w", err)
}
config, err := s.getApplicationClusterConfig(ctx, a)
if err != nil {
return err
return fmt.Errorf("error getting application cluster config: %w", err)
}
serverVersion, err := s.kubectl.GetServerVersion(config)
if err != nil {
return err
return fmt.Errorf("error getting server version: %w", err)
}
apiResources, err := s.kubectl.GetAPIResources(config, false, kubecache.NewNoopSettings())
if err != nil {
return err
return fmt.Errorf("error getting API resources: %w", err)
}
manifestInfo, err = client.GenerateManifest(ctx, &apiclient.ManifestRequest{
@@ -347,7 +347,10 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
TrackingMethod: string(argoutil.GetTrackingMethod(s.settingsMgr)),
EnabledSourceTypes: enableGenerateManifests,
})
return err
if err != nil {
return fmt.Errorf("error generating manifests: %w", err)
}
return nil
})
if err != nil {
@@ -358,16 +361,16 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
obj := &unstructured.Unstructured{}
err = json.Unmarshal([]byte(manifest), obj)
if err != nil {
return nil, err
return nil, fmt.Errorf("error unmarshaling manifest into unstructured: %w", err)
}
if obj.GetKind() == kube.SecretKind && obj.GroupVersionKind().Group == "" {
obj, _, err = diff.HideSecretData(obj, nil)
if err != nil {
return nil, err
return nil, fmt.Errorf("error hiding secret data: %w", err)
}
data, err := json.Marshal(obj)
if err != nil {
return nil, err
return nil, fmt.Errorf("error marshaling manifest: %w", err)
}
manifestInfo.Manifests[i] = string(data)
}
@@ -386,7 +389,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
})
if err != nil {
return nil, err
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
@@ -410,7 +413,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
app, err := argoutil.RefreshApp(appIf, *q.Name, refreshType)
if err != nil {
return nil, err
return nil, fmt.Errorf("error refreshing the app: %w", err)
}
if refreshType == appv1.RefreshTypeHard {
@@ -468,7 +471,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
func (s *Server) ListResourceEvents(ctx context.Context, q *application.ApplicationResourceEventsQuery) (*v1.EventList, error) {
a, err := s.appLister.Get(*q.Name)
if err != nil {
return nil, err
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
@@ -492,7 +495,7 @@ func (s *Server) ListResourceEvents(ctx context.Context, q *application.Applicat
} else {
tree, err := s.GetAppResources(ctx, a)
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting app resources: %w", err)
}
found := false
for _, n := range append(tree.Nodes, tree.OrphanedNodes...) {
@@ -509,11 +512,11 @@ func (s *Server) ListResourceEvents(ctx context.Context, q *application.Applicat
var config *rest.Config
config, err = s.getApplicationClusterConfig(ctx, a)
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting application cluster config: %w", err)
}
kubeClientset, err = kubernetes.NewForConfig(config)
if err != nil {
return nil, err
return nil, fmt.Errorf("error creating kube client: %w", err)
}
fieldSelector = fields.SelectorFromSet(map[string]string{
"involvedObject.name": q.GetResourceName(),
@@ -523,7 +526,11 @@ func (s *Server) ListResourceEvents(ctx context.Context, q *application.Applicat
}
log.Infof("Querying for resource events with field selector: %s", fieldSelector)
opts := metav1.ListOptions{FieldSelector: fieldSelector}
return kubeClientset.CoreV1().Events(namespace).List(ctx, opts)
list, err := kubeClientset.CoreV1().Events(namespace).List(ctx, opts)
if err != nil {
return nil, fmt.Errorf("error listing resource events: %w", err)
}
return list, nil
}
func (s *Server) validateAndUpdateApp(ctx context.Context, newApp *appv1.Application, merge bool, validate bool) (*appv1.Application, error) {
@@ -532,15 +539,19 @@ func (s *Server) validateAndUpdateApp(ctx context.Context, newApp *appv1.Applica
app, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, newApp.Name, metav1.GetOptions{})
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting application: %w", err)
}
err = s.validateAndNormalizeApp(ctx, newApp, validate)
if err != nil {
return nil, err
return nil, fmt.Errorf("error validating and normalizing app: %w", err)
}
return s.updateApp(app, newApp, ctx, merge)
a, err := s.updateApp(app, newApp, ctx, merge)
if err != nil {
return nil, fmt.Errorf("error updating application: %w", err)
}
return a, nil
}
func mergeStringMaps(items ...map[string]string) map[string]string {
@@ -614,7 +625,7 @@ func (s *Server) updateApp(app *appv1.Application, newApp *appv1.Application, ct
app, err = s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, newApp.Name, metav1.GetOptions{})
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting application: %w", err)
}
}
return nil, status.Errorf(codes.Internal, "Failed to update application. Too many conflicts")
@@ -640,7 +651,7 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
}
a, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, *q.Name, metav1.GetOptions{})
if err != nil {
return nil, err
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
@@ -652,7 +663,7 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
}
a, err = s.validateAndUpdateApp(ctx, a, false, validate)
if err != nil {
return nil, err
return nil, fmt.Errorf("error validating and updating app: %w", err)
}
return &a.Spec, nil
}
@@ -662,7 +673,7 @@ func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchReque
app, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, *q.Name, metav1.GetOptions{})
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting application: %w", err)
}
if err = s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, apputil.AppRBACName(*app)); err != nil {
@@ -671,7 +682,7 @@ func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchReque
jsonApp, err := json.Marshal(app)
if err != nil {
return nil, err
return nil, fmt.Errorf("error marshaling application: %w", err)
}
var patchApp []byte
@@ -680,16 +691,16 @@ func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchReque
case "json", "":
patch, err := jsonpatch.DecodePatch([]byte(q.GetPatch()))
if err != nil {
return nil, err
return nil, fmt.Errorf("error decoding json patch: %w", err)
}
patchApp, err = patch.Apply(jsonApp)
if err != nil {
return nil, err
return nil, fmt.Errorf("error applying patch: %w", err)
}
case "merge":
patchApp, err = jsonpatch.MergePatch(jsonApp, []byte(q.GetPatch()))
if err != nil {
return nil, err
return nil, fmt.Errorf("error calculating merge patch: %w", err)
}
default:
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Patch type '%s' is not supported", q.GetPatchType()))
@@ -698,7 +709,7 @@ func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchReque
newApp := &v1alpha1.Application{}
err = json.Unmarshal(patchApp, newApp)
if err != nil {
return nil, err
return nil, fmt.Errorf("error unmarshaling patched app: %w", err)
}
return s.validateAndUpdateApp(ctx, newApp, false, true)
}
@@ -707,7 +718,7 @@ func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchReque
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{})
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting application: %w", err)
}
s.projectLock.RLock(a.Spec.Project)
@@ -749,17 +760,17 @@ func (s *Server) Delete(ctx context.Context, q *application.ApplicationDeleteReq
},
})
if err != nil {
return nil, err
return nil, fmt.Errorf("error marshaling finalizers: %w", err)
}
_, err = s.appclientset.ArgoprojV1alpha1().Applications(a.Namespace).Patch(ctx, a.Name, types.MergePatchType, patch, metav1.PatchOptions{})
if err != nil {
return nil, err
return nil, fmt.Errorf("error patching application with finalizers: %w", err)
}
}
err = s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Delete(ctx, *q.Name, metav1.DeleteOptions{})
if err != nil {
return nil, err
return nil, fmt.Errorf("error deleting application: %w", err)
}
s.logAppEvent(a, ctx, argo.EventReasonResourceDeleted, "deleted application")
return &application.ApplicationResponse{}, nil
@@ -777,7 +788,7 @@ func (s *Server) Watch(q *application.ApplicationQuery, ws application.Applicati
claims := ws.Context().Value("claims")
selector, err := labels.Parse(q.GetSelector())
if err != nil {
return err
return fmt.Errorf("error parsing labels with selectors: %w", err)
}
minVersion := 0
if q.GetResourceVersion() != "" {
@@ -823,7 +834,7 @@ func (s *Server) Watch(q *application.ApplicationQuery, ws application.Applicati
if q.GetResourceVersion() == "" || q.GetName() != "" {
apps, err := s.appLister.List(selector)
if err != nil {
return err
return fmt.Errorf("error listing apps with selector: %w", err)
}
sort.Slice(apps, func(i, j int) bool {
return apps[i].Name < apps[j].Name
@@ -850,12 +861,12 @@ func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Applica
if apierr.IsNotFound(err) {
return status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", app.Spec.Project)
}
return err
return fmt.Errorf("error getting application's project: %w", err)
}
currApp, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, app.Name, metav1.GetOptions{})
if err != nil {
if !apierr.IsNotFound(err) {
return err
return fmt.Errorf("error getting application by name: %w", err)
}
// Kubernetes go-client will return a pointer to a zero-value app instead of nil, even
// though the API response was NotFound. This behavior was confirmed via logs.
@@ -876,15 +887,15 @@ func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Applica
// If source is Kustomize add build options
kustomizeSettings, err := s.settingsMgr.GetKustomizeSettings()
if err != nil {
return err
return fmt.Errorf("error getting kustomize settings: %w", err)
}
kustomizeOptions, err := kustomizeSettings.GetOptions(app.Spec.Source)
if err != nil {
return err
return fmt.Errorf("error getting kustomize options from settings: %w", err)
}
plugins, err := s.plugins()
if err != nil {
return err
return fmt.Errorf("error getting plugins: %w", err)
}
if err := argo.ValidateDestination(ctx, &app.Spec.Destination, s.db); err != nil {
@@ -895,7 +906,7 @@ func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Applica
if validate {
conditions, err = argo.ValidateRepo(ctx, app, s.repoClientset, s.db, kustomizeOptions, plugins, s.kubectl, proj, s.settingsMgr)
if err != nil {
return err
return fmt.Errorf("error validating the repo: %w", err)
}
if len(conditions) > 0 {
return status.Errorf(codes.InvalidArgument, "application spec for %s is invalid: %s", app.Name, argo.FormatAppConditions(conditions))
@@ -904,7 +915,7 @@ func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Applica
conditions, err = argo.ValidatePermissions(ctx, &app.Spec, proj, s.db)
if err != nil {
return err
return fmt.Errorf("error validating project permissions: %w", err)
}
if len(conditions) > 0 {
return status.Errorf(codes.InvalidArgument, "application spec for %s is invalid: %s", app.Name, argo.FormatAppConditions(conditions))
@@ -916,11 +927,11 @@ func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Applica
func (s *Server) getApplicationClusterConfig(ctx context.Context, a *appv1.Application) (*rest.Config, error) {
if err := argo.ValidateDestination(ctx, &a.Spec.Destination, s.db); err != nil {
return nil, err
return nil, fmt.Errorf("error validating destination: %w", err)
}
clst, err := s.db.GetCluster(ctx, a.Spec.Destination.Server)
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting cluster: %w", err)
}
config := clst.RESTConfig()
return config, err
@@ -942,7 +953,7 @@ func (s *Server) getCachedAppState(ctx context.Context, a *appv1.Application, ge
Refresh: pointer.StringPtr(string(appv1.RefreshTypeNormal)),
})
if err != nil {
return err
return fmt.Errorf("error getting application by query: %w", err)
}
return getFromCache()
}
@@ -954,13 +965,16 @@ func (s *Server) GetAppResources(ctx context.Context, a *appv1.Application) (*ap
err := s.getCachedAppState(ctx, a, func() error {
return s.cache.GetAppResourcesTree(a.Name, &tree)
})
return &tree, err
if err != nil {
return &tree, fmt.Errorf("error getting cached app state: %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)
if err != nil {
return nil, nil, nil, err
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
@@ -968,7 +982,7 @@ func (s *Server) getAppLiveResource(ctx context.Context, action string, q *appli
tree, err := s.GetAppResources(ctx, a)
if err != nil {
return nil, nil, nil, err
return nil, nil, nil, fmt.Errorf("error getting app resources: %w", err)
}
found := tree.FindNode(q.GetGroup(), q.GetKind(), q.GetNamespace(), q.GetResourceName())
@@ -977,7 +991,7 @@ func (s *Server) getAppLiveResource(ctx context.Context, action string, q *appli
}
config, err := s.getApplicationClusterConfig(ctx, a)
if err != nil {
return nil, nil, nil, err
return nil, nil, nil, fmt.Errorf("error getting application cluster config: %w", err)
}
return found, config, a, nil
}
@@ -985,7 +999,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, err
return nil, fmt.Errorf("error getting app live resource: %w", err)
}
// make sure to use specified resource version if provided
@@ -994,15 +1008,15 @@ func (s *Server) GetResource(ctx context.Context, q *application.ApplicationReso
}
obj, err := s.kubectl.GetResource(ctx, config, res.GroupKindVersion(), res.Name, res.Namespace)
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting resource: %w", err)
}
obj, err = replaceSecretValues(obj)
if err != nil {
return nil, err
return nil, fmt.Errorf("error replacing secret values: %w", err)
}
data, err := json.Marshal(obj.Object)
if err != nil {
return nil, err
return nil, fmt.Errorf("error marshaling object: %w", err)
}
manifest := string(data)
return &application.ApplicationResourceResponse{Manifest: &manifest}, nil
@@ -1031,7 +1045,7 @@ 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, err
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
@@ -1043,15 +1057,15 @@ func (s *Server) PatchResource(ctx context.Context, q *application.ApplicationRe
if res.Kind == kube.SecretKind && res.Group == "" {
return nil, fmt.Errorf("failed to patch Secret %s/%s", res.Namespace, res.Name)
}
return nil, err
return nil, fmt.Errorf("error patching resource: %w", err)
}
manifest, err = replaceSecretValues(manifest)
if err != nil {
return nil, err
return nil, fmt.Errorf("error replacing secret values: %w", err)
}
data, err := json.Marshal(manifest.Object)
if err != nil {
return nil, err
return nil, fmt.Errorf("erro marshaling manifest object: %w", err)
}
s.logAppEvent(a, ctx, argo.EventReasonResourceUpdated, fmt.Sprintf("patched resource %s/%s '%s'", q.GetGroup(), q.GetKind(), q.GetResourceName()))
m := string(data)
@@ -1072,7 +1086,7 @@ 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, err
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
@@ -1091,7 +1105,7 @@ func (s *Server) DeleteResource(ctx context.Context, q *application.ApplicationR
}
err = s.kubectl.DeleteResource(ctx, config, res.GroupKindVersion(), res.Name, res.Namespace, deleteOption)
if err != nil {
return nil, err
return nil, fmt.Errorf("error deleting resource: %w", err)
}
s.logAppEvent(a, ctx, argo.EventReasonResourceDeleted, fmt.Sprintf("deleted resource %s/%s '%s'", q.GetGroup(), q.GetKind(), q.GetResourceName()))
return &application.ApplicationResponse{}, nil
@@ -1100,7 +1114,7 @@ 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())
if err != nil {
return nil, err
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
@@ -1111,7 +1125,7 @@ func (s *Server) ResourceTree(ctx context.Context, q *application.ResourcesQuery
func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application.ApplicationService_WatchResourceTreeServer) error {
a, err := s.appLister.Get(q.GetApplicationName())
if err != nil {
return err
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 {
@@ -1122,7 +1136,7 @@ func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application
var tree appv1.ApplicationTree
err := s.cache.GetAppResourcesTree(q.GetApplicationName(), &tree)
if err != nil {
return err
return fmt.Errorf("error getting app resource tree: %w", err)
}
return ws.Send(&tree)
})
@@ -1131,24 +1145,24 @@ 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())
if err != nil {
return nil, err
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)
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting repository by URL: %w", err)
}
// We need to get some information with the project associated to the app,
// so we'll know whether GPG signatures are enforced.
proj, err := argo.GetAppProject(&a.Spec, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), a.Namespace, s.settingsMgr, s.db, ctx)
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting app project: %w", err)
}
conn, repoClient, err := s.repoClientset.NewRepoServerClient()
if err != nil {
return nil, err
return nil, fmt.Errorf("error creating repo server client: %w", err)
}
defer ioutil.Close(conn)
return repoClient.GetRevisionMetadata(ctx, &apiclient.RepoServerRevisionMetadataRequest{
@@ -1168,17 +1182,17 @@ 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)
if err != nil {
return nil, fmt.Errorf("error getting application: %s", err)
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: %s", err)
return nil, fmt.Errorf("error verifying rbac: %w", 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: %s", err)
return nil, fmt.Errorf("error getting cached app state: %w", err)
}
res := &application.ManagedResourcesResponse{}
for i := range items {
@@ -1227,7 +1241,7 @@ func (s *Server) PodLogs(q *application.ApplicationPodLogsQuery, ws application.
a, err := s.appLister.Get(q.GetName())
if err != nil {
return err
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 {
@@ -1241,7 +1255,7 @@ func (s *Server) PodLogs(q *application.ApplicationPodLogsQuery, ws application.
// In the future, logs RBAC will be always enforced and the parameter along with this check will be removed
serverRBACLogEnforceEnable, err := s.settingsMgr.GetServerRBACLogEnforceEnable()
if err != nil {
return err
return fmt.Errorf("error getting RBAC log enforce enable: %w", err)
}
if serverRBACLogEnforceEnable {
@@ -1252,17 +1266,17 @@ func (s *Server) PodLogs(q *application.ApplicationPodLogsQuery, ws application.
tree, err := s.GetAppResources(ws.Context(), a)
if err != nil {
return err
return fmt.Errorf("error getting app resource tree: %w", err)
}
config, err := s.getApplicationClusterConfig(ws.Context(), a)
if err != nil {
return err
return fmt.Errorf("error getting application cluster config: %w", err)
}
kubeClientset, err := kubernetes.NewForConfig(config)
if err != nil {
return err
return fmt.Errorf("error creating kube client: %w", err)
}
// from the tree find pods which match query of kind, group, and resource name
@@ -1425,7 +1439,7 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
appIf := s.appclientset.ArgoprojV1alpha1().Applications(s.ns)
a, err := appIf.Get(ctx, *syncReq.Name, metav1.GetOptions{})
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting application by name: %w", err)
}
proj, err := argo.GetAppProject(&a.Spec, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), a.Namespace, s.settingsMgr, s.db, ctx)
@@ -1433,11 +1447,11 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
if apierr.IsNotFound(err) {
return a, status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", a.Spec.Project)
}
return a, err
return a, fmt.Errorf("error getting app project: %w", err)
}
if !proj.Spec.SyncWindows.Matches(a).CanSync(true) {
return a, status.Errorf(codes.PermissionDenied, "Cannot sync: Blocked by sync window")
return a, status.Errorf(codes.PermissionDenied, "cannot sync: blocked by sync window")
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionSync, apputil.AppRBACName(*a)); err != nil {
@@ -1448,7 +1462,7 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
return nil, err
}
if a.Spec.SyncPolicy != nil && a.Spec.SyncPolicy.Automated != nil && !syncReq.GetDryRun() {
return nil, status.Error(codes.FailedPrecondition, "Cannot use local sync when Automatic Sync Policy is enabled unless for dry run")
return nil, status.Error(codes.FailedPrecondition, "cannot use local sync when Automatic Sync Policy is enabled unless for dry run")
}
}
if a.DeletionTimestamp != nil {
@@ -1508,25 +1522,26 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
}
a, err = argo.SetAppOperation(appIf, *syncReq.Name, &op)
if err == nil {
partial := ""
if len(syncReq.Resources) > 0 {
partial = "partial "
}
reason := fmt.Sprintf("initiated %ssync to %s", partial, displayRevision)
if syncReq.Manifests != nil {
reason = fmt.Sprintf("initiated %ssync locally", partial)
}
s.logAppEvent(a, ctx, argo.EventReasonOperationStarted, reason)
if err != nil {
return nil, fmt.Errorf("error setting app operation: %w", err)
}
return a, err
partial := ""
if len(syncReq.Resources) > 0 {
partial = "partial "
}
reason := fmt.Sprintf("initiated %ssync to %s", partial, displayRevision)
if syncReq.Manifests != nil {
reason = fmt.Sprintf("initiated %ssync locally", partial)
}
s.logAppEvent(a, ctx, argo.EventReasonOperationStarted, reason)
return a, nil
}
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{})
if err != nil {
return nil, err
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
@@ -1571,10 +1586,11 @@ func (s *Server) Rollback(ctx context.Context, rollbackReq *application.Applicat
},
}
a, err = argo.SetAppOperation(appIf, *rollbackReq.Name, &op)
if err == nil {
s.logAppEvent(a, ctx, argo.EventReasonOperationStarted, fmt.Sprintf("initiated rollback to %d", rollbackReq.GetId()))
if err != nil {
return nil, fmt.Errorf("error setting app operation: %w", err)
}
return a, err
s.logAppEvent(a, ctx, argo.EventReasonOperationStarted, fmt.Sprintf("initiated rollback to %d", rollbackReq.GetId()))
return a, nil
}
// resolveRevision resolves the revision specified either in the sync request, or the
@@ -1589,11 +1605,11 @@ func (s *Server) resolveRevision(ctx context.Context, app *appv1.Application, sy
}
repo, err := s.db.GetRepository(ctx, app.Spec.Source.RepoURL)
if err != nil {
return "", "", err
return "", "", fmt.Errorf("error getting repository by URL: %w", err)
}
conn, repoClient, err := s.repoClientset.NewRepoServerClient()
if err != nil {
return "", "", err
return "", "", fmt.Errorf("error getting repo server client: %w", err)
}
defer io.Close(conn)
@@ -1610,7 +1626,7 @@ func (s *Server) resolveRevision(ctx context.Context, app *appv1.Application, sy
AmbiguousRevision: ambiguousRevision,
})
if err != nil {
return "", "", err
return "", "", fmt.Errorf("error resolving repo revision: %w", err)
}
return resolveRevisionResponse.Revision, resolveRevisionResponse.AmbiguousRevision, nil
}
@@ -1618,7 +1634,7 @@ 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{})
if err != nil {
return nil, err
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
@@ -1636,13 +1652,13 @@ func (s *Server) TerminateOperation(ctx context.Context, termOpReq *application.
return &application.OperationTerminateResponse{}, nil
}
if !apierr.IsConflict(err) {
return nil, err
return nil, fmt.Errorf("error updating application: %w", err)
}
log.Warnf("Failed to set operation for app '%s' due to update conflict. Retrying again...", *termOpReq.Name)
log.Warnf("failed to set operation for app %q due to update conflict. retrying again...", *termOpReq.Name)
time.Sleep(100 * time.Millisecond)
a, err = s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, *termOpReq.Name, metav1.GetOptions{})
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting application by name: %w", err)
}
}
return nil, status.Errorf(codes.Internal, "Failed to terminate app. Too many conflicts")
@@ -1671,24 +1687,24 @@ 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, err
return nil, fmt.Errorf("error getting app live resource: %w", err)
}
obj, err := s.kubectl.GetResource(ctx, config, res.GroupKindVersion(), res.Name, res.Namespace)
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting resource: %w", err)
}
resourceOverrides, err := s.settingsMgr.GetResourceOverrides()
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting resource overrides: %w", err)
}
availableActions, err := s.getAvailableActions(resourceOverrides, obj)
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting available actions: %w", err)
}
actionsPtr := []*appv1.ResourceAction{}
for _, action := range availableActions {
actionsPtr = append(actionsPtr, &action)
for i := range availableActions {
actionsPtr = append(actionsPtr, &availableActions[i])
}
return &application.ResourceActionsListResponse{Actions: actionsPtr}, nil
@@ -1701,14 +1717,14 @@ func (s *Server) getAvailableActions(resourceOverrides map[string]appv1.Resource
discoveryScript, err := luaVM.GetResourceActionDiscovery(obj)
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting Lua discovery script: %w", err)
}
if discoveryScript == "" {
return []appv1.ResourceAction{}, nil
}
availableActions, err := luaVM.ExecuteResourceActionDiscovery(obj, discoveryScript)
if err != nil {
return nil, err
return nil, fmt.Errorf("error executing Lua discovery script: %w", err)
}
return availableActions, nil
@@ -1726,16 +1742,16 @@ 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, err
return nil, fmt.Errorf("error getting app live resource: %w", err)
}
liveObj, err := s.kubectl.GetResource(ctx, config, res.GroupKindVersion(), res.Name, res.Namespace)
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting resource: %w", err)
}
resourceOverrides, err := s.settingsMgr.GetResourceOverrides()
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting resource overrides: %w", err)
}
luaVM := lua.VM{
@@ -1743,27 +1759,27 @@ func (s *Server) RunResourceAction(ctx context.Context, q *application.ResourceA
}
action, err := luaVM.GetResourceAction(liveObj, q.GetAction())
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting Lua resource action: %w", err)
}
newObj, err := luaVM.ExecuteResourceAction(liveObj, action.ActionLua)
if err != nil {
return nil, err
return nil, fmt.Errorf("error executing Lua resource action: %w", err)
}
newObjBytes, err := json.Marshal(newObj)
if err != nil {
return nil, err
return nil, fmt.Errorf("error marshaling new object: %w", err)
}
liveObjBytes, err := json.Marshal(liveObj)
if err != nil {
return nil, err
return nil, fmt.Errorf("error marshaling live object: %w", err)
}
diffBytes, err := jsonpatch.CreateMergePatch(liveObjBytes, newObjBytes)
if err != nil {
return nil, err
return nil, fmt.Errorf("error calculating merge patch: %w", err)
}
if string(diffBytes) == "{}" {
return &application.ApplicationResponse{}, nil
@@ -1778,13 +1794,13 @@ func (s *Server) RunResourceAction(ctx context.Context, q *application.ResourceA
// * the other to update only status.
nonStatusPatch, statusPatch, err := splitStatusPatch(diffBytes)
if err != nil {
return nil, err
return nil, fmt.Errorf("error splitting status patch: %w", err)
}
if statusPatch != nil {
_, err = s.kubectl.PatchResource(ctx, config, newObj.GroupVersionKind(), newObj.GetName(), newObj.GetNamespace(), types.MergePatchType, diffBytes, "status")
if err != nil {
if !apierr.IsNotFound(err) {
return nil, err
return nil, fmt.Errorf("error patching resource: %w", err)
}
// K8s API server returns 404 NotFound when the CRD does not support the status subresource
// if we get here, the CRD does not use the status subresource. We will fall back to a normal patch
@@ -1797,7 +1813,7 @@ func (s *Server) RunResourceAction(ctx context.Context, q *application.ResourceA
if diffBytes != nil {
_, err = s.kubectl.PatchResource(ctx, config, newObj.GroupVersionKind(), newObj.GetName(), newObj.GetNamespace(), types.MergePatchType, diffBytes)
if err != nil {
return nil, err
return nil, fmt.Errorf("error patching resource: %w", err)
}
}
@@ -1842,7 +1858,7 @@ func splitStatusPatch(patch []byte) ([]byte, []byte, error) {
func (s *Server) plugins() ([]*v1alpha1.ConfigManagementPlugin, error) {
plugins, err := s.settingsMgr.GetConfigManagementPlugins()
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting config management plugin: %w", err)
}
tools := make([]*v1alpha1.ConfigManagementPlugin, len(plugins))
for i, p := range plugins {
@@ -1856,7 +1872,7 @@ func (s *Server) GetApplicationSyncWindows(ctx context.Context, q *application.A
appIf := s.appclientset.ArgoprojV1alpha1().Applications(s.ns)
a, err := appIf.Get(ctx, *q.Name, metav1.GetOptions{})
if err != nil {
return nil, err
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 {
@@ -1865,7 +1881,7 @@ func (s *Server) GetApplicationSyncWindows(ctx context.Context, q *application.A
proj, err := argo.GetAppProject(&a.Spec, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), a.Namespace, s.settingsMgr, s.db, ctx)
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting app project: %w", err)
}
windows := proj.Spec.SyncWindows.Matches(a)

View File

@@ -44,9 +44,9 @@ message RevisionMetadataQuery{
// ApplicationEventsQuery is a query for application resource events
message ApplicationResourceEventsQuery {
required string name = 1;
required string resourceNamespace = 2;
required string resourceName = 3;
required string resourceUID = 4;
optional string resourceNamespace = 2;
optional string resourceName = 3;
optional string resourceUID = 4;
}
// ManifestQuery is a query for manifest resources
@@ -115,7 +115,7 @@ message ApplicationRollbackRequest {
message ApplicationResourceRequest {
required string name = 1;
required string namespace = 2;
optional string namespace = 2;
required string resourceName = 3;
required string version = 4;
optional string group = 5;
@@ -124,7 +124,7 @@ message ApplicationResourceRequest {
message ApplicationResourcePatchRequest {
required string name = 1;
required string namespace = 2;
optional string namespace = 2;
required string resourceName = 3;
required string version = 4;
optional string group = 5;
@@ -135,7 +135,7 @@ message ApplicationResourcePatchRequest {
message ApplicationResourceDeleteRequest {
required string name = 1;
required string namespace = 2;
optional string namespace = 2;
required string resourceName = 3;
required string version = 4;
optional string group = 5;
@@ -146,7 +146,7 @@ message ApplicationResourceDeleteRequest {
message ResourceActionRunRequest {
required string name = 1;
required string namespace = 2;
optional string namespace = 2;
required string resourceName = 3;
required string version = 4;
optional string group = 5;
@@ -164,7 +164,7 @@ message ApplicationResourceResponse {
message ApplicationPodLogsQuery {
required string name = 1;
required string namespace = 2;
optional string namespace = 2;
optional string podName = 3;
optional string container = 4;
optional int64 sinceSeconds = 5;

View File

@@ -41,6 +41,7 @@ import (
"github.com/argoproj/argo-cd/v2/util/cache"
"github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v2/util/errors"
"github.com/argoproj/argo-cd/v2/util/grpc"
"github.com/argoproj/argo-cd/v2/util/rbac"
"github.com/argoproj/argo-cd/v2/util/settings"
)
@@ -643,7 +644,9 @@ 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})
assert.Equal(t, status.Code(err), codes.PermissionDenied)
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(`
@@ -659,7 +662,9 @@ 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})
assert.Equal(t, status.Code(err), codes.PermissionDenied)
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(`

View File

@@ -2,11 +2,15 @@ package application
import (
"context"
"fmt"
"io"
"net/http"
"github.com/argoproj/gitops-engine/pkg/utils/kube"
log "github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
@@ -17,10 +21,10 @@ import (
applisters "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
servercache "github.com/argoproj/argo-cd/v2/server/cache"
"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
apputil "github.com/argoproj/argo-cd/v2/util/app"
"github.com/argoproj/argo-cd/v2/util/argo"
"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"
)
type terminalHandler struct {
@@ -54,27 +58,76 @@ func (s *terminalHandler) getApplicationClusterRawConfig(ctx context.Context, a
return clst.RawRestConfig(), nil
}
// isValidPodName checks that a podName is valid
func isValidPodName(name string) bool {
// https://github.com/kubernetes/kubernetes/blob/976a940f4a4e84fe814583848f97b9aafcdb083f/pkg/apis/core/validation/validation.go#L241
validationErrors := apimachineryvalidation.NameIsDNSSubdomain(name, false)
return len(validationErrors) == 0
}
func isValidAppName(name string) bool {
// app names have the same rules as pods.
return isValidPodName(name)
}
func isValidProjectName(name string) bool {
// project names have the same rules as pods.
return isValidPodName(name)
}
// isValidNamespaceName checks that a namespace name is valid
func isValidNamespaceName(name string) bool {
// https://github.com/kubernetes/kubernetes/blob/976a940f4a4e84fe814583848f97b9aafcdb083f/pkg/apis/core/validation/validation.go#L262
validationErrors := apimachineryvalidation.ValidateNamespaceName(name, false)
return len(validationErrors) == 0
}
// isValidContainerName checks that a containerName is valid
func isValidContainerName(name string) bool {
// https://github.com/kubernetes/kubernetes/blob/53a9d106c4aabcd550cc32ae4e8004f32fb0ae7b/pkg/api/validation/validation.go#L280
validationErrors := apimachineryvalidation.NameIsDNSLabel(name, false)
return len(validationErrors) == 0
}
func (s *terminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
podName := q.Get("pod")
container := q.Get("container")
app := q.Get("appName")
project := q.Get("projectName")
namespace := q.Get("namespace")
shell := q.Get("shell")
if podName == "" || container == "" || app == "" || namespace == "" {
if podName == "" || container == "" || app == "" || project == "" || namespace == "" {
http.Error(w, "Missing required parameters", http.StatusBadRequest)
return
}
ctx := r.Context()
a, err := s.appLister.Get(app)
if err != nil {
http.Error(w, "Cannot get app", http.StatusBadRequest)
if !isValidPodName(podName) {
http.Error(w, "Pod name is not valid", http.StatusBadRequest)
return
}
if !isValidContainerName(container) {
http.Error(w, "Container name is not valid", http.StatusBadRequest)
return
}
if !isValidAppName(app) {
http.Error(w, "App name is not valid", http.StatusBadRequest)
return
}
if !isValidProjectName(project) {
http.Error(w, "Project name is not valid", http.StatusBadRequest)
return
}
if !isValidNamespaceName(namespace) {
http.Error(w, "Namespace name is not valid", http.StatusBadRequest)
return
}
shell := q.Get("shell") // No need to validate. Will only buse used if it's in the allow-list.
appRBACName := apputil.AppRBACName(*a)
ctx := r.Context()
appRBACName := fmt.Sprintf("%s/%s", project, app)
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, appRBACName); err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
@@ -85,6 +138,26 @@ func (s *terminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
fieldLog := log.WithFields(log.Fields{"application": app, "userName": sessionmgr.Username(ctx), "container": container,
"podName": podName, "namespace": namespace, "cluster": project})
a, err := s.appLister.Get(app)
if err != nil {
if apierr.IsNotFound(err) {
http.Error(w, "App not found", http.StatusNotFound)
return
}
fieldLog.Errorf("Error when getting app %q when launching a terminal: %s", app, err)
http.Error(w, "Cannot get app", http.StatusInternalServerError)
return
}
if a.Spec.Project != project {
fieldLog.Warnf("The wrong project (%q) was specified for the app %q when launching a terminal", project, app)
http.Error(w, "The wrong project was specified for the app", http.StatusBadRequest)
return
}
config, err := s.getApplicationClusterRawConfig(ctx, a)
if err != nil {
http.Error(w, "Cannot get raw cluster config", http.StatusBadRequest)
@@ -111,6 +184,7 @@ func (s *terminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
pod, err := kubeClientset.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{})
if err != nil {
fieldLog.Errorf("error retrieving pod: %s", err)
http.Error(w, "Cannot find pod", http.StatusBadRequest)
return
}
@@ -128,10 +202,13 @@ func (s *terminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
if !findContainer {
fieldLog.Warn("terminal container not found")
http.Error(w, "Cannot find container", http.StatusBadRequest)
return
}
fieldLog.Info("terminal session starting")
session, err := newTerminalSession(w, r, nil)
if err != nil {
http.Error(w, "Failed to start terminal session", http.StatusBadRequest)

View File

@@ -1,9 +1,13 @@
package application
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/argoproj/gitops-engine/pkg/utils/kube"
"github.com/stretchr/testify/assert"
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
@@ -74,3 +78,146 @@ func TestPodExists(t *testing.T) {
})
}
}
func TestIsValidPodName(t *testing.T) {
for _, tcase := range []struct {
name string
resourceName string
expectedResult bool
}{
{
name: "valid pod name",
resourceName: "argocd-server-794644486d-r8v9d",
expectedResult: true,
},
{
name: "not valid contains spaces",
resourceName: "kubectl delete pods",
expectedResult: false,
},
{
name: "not valid",
resourceName: "kubectl -n kube-system delete pods --all",
expectedResult: false,
},
{
name: "not valid contains special characters",
resourceName: "delete+*+from+etcd%3b",
expectedResult: false,
},
} {
t.Run(tcase.name, func(t *testing.T) {
result := isValidPodName(tcase.resourceName)
if result != tcase.expectedResult {
t.Errorf("Expected result %v, but got %v", tcase.expectedResult, result)
}
})
}
}
func TestIsValidNamespaceName(t *testing.T) {
for _, tcase := range []struct {
name string
resourceName string
expectedResult bool
}{
{
name: "valid pod namespace name",
resourceName: "argocd",
expectedResult: true,
},
{
name: "not valid contains spaces",
resourceName: "kubectl delete ns argocd",
expectedResult: false,
},
{
name: "not valid contains special characters",
resourceName: "delete+*+from+etcd%3b",
expectedResult: false,
},
} {
t.Run(tcase.name, func(t *testing.T) {
result := isValidNamespaceName(tcase.resourceName)
if result != tcase.expectedResult {
t.Errorf("Expected result %v, but got %v", tcase.expectedResult, result)
}
})
}
}
func TestIsValidContainerNameName(t *testing.T) {
for _, tcase := range []struct {
name string
resourceName string
expectedResult bool
}{
{
name: "valid container name",
resourceName: "argocd-server",
expectedResult: true,
},
{
name: "not valid contains spaces",
resourceName: "kubectl delete pods",
expectedResult: false,
},
{
name: "not valid contains special characters",
resourceName: "delete+*+from+etcd%3b",
expectedResult: false,
},
} {
t.Run(tcase.name, func(t *testing.T) {
result := isValidContainerName(tcase.resourceName)
if result != tcase.expectedResult {
t.Errorf("Expected result %v, but got %v", tcase.expectedResult, result)
}
})
}
}
func TestTerminalHandler_ServeHTTP_empty_params(t *testing.T) {
testKeys := []string{
"pod",
"container",
"app",
"project",
"namespace",
}
// test both empty and invalid
testValues := []string{"", "invalid%20name"}
for _, testKey := range testKeys {
testKeyCopy := testKey
for _, testValue := range testValues {
testValueCopy := testValue
t.Run(testKeyCopy+ " " + testValueCopy, func(t *testing.T) {
t.Parallel()
handler := terminalHandler{}
params := map[string]string{
"pod": "valid",
"container": "valid",
"app": "valid",
"project": "valid",
"namespace": "valid",
}
params[testKeyCopy] = testValueCopy
var paramsArray []string
for key, value := range params {
paramsArray = append(paramsArray, key + "=" + value)
}
paramsString := strings.Join(paramsArray, "&")
request := httptest.NewRequest("GET", "https://argocd.example.com/api/v1/terminal?" + paramsString, nil)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, request)
response := recorder.Result()
assert.Equal(t, http.StatusBadRequest, response.StatusCode)
})
}
}
}

View File

@@ -721,6 +721,8 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
var dOpts []grpc.DialOption
dOpts = append(dOpts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(apiclient.MaxGRPCMessageSize)))
dOpts = append(dOpts, grpc.WithUserAgent(fmt.Sprintf("%s/%s", common.ArgoCDUserAgentName, common.GetVersion().Version)))
dOpts = append(dOpts, grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()))
dOpts = append(dOpts, grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()))
if a.useTLS() {
// The following sets up the dial Options for grpc-gateway to talk to gRPC server over TLS.
// grpc-gateway is just translating HTTP/HTTPS requests as gRPC requests over localhost,
@@ -752,34 +754,41 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
handler = compressHandler(handler)
}
mux.Handle("/api/", handler)
if a.settings.ExecEnabled {
terminalHandler := application.NewHandler(a.appLister, a.db, a.enf, a.Cache, appResourceTreeFn)
mux.HandleFunc("/terminal", func(writer http.ResponseWriter, request *http.Request) {
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 := application.NewHandler(a.appLister, a.db, a.enf, a.Cache, appResourceTreeFn)
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)
})
} else {
log.Info("exec is disabled, and /terminal will return a 404")
}
}
terminalHandler.ServeHTTP(writer, request)
})
mustRegisterGWHandler(versionpkg.RegisterVersionServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts)
mustRegisterGWHandler(clusterpkg.RegisterClusterServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts)
@@ -1000,6 +1009,8 @@ func (a *ArgoCDServer) Authenticate(ctx context.Context) (context.Context, error
}
if !argoCDSettings.AnonymousUserEnabled {
return ctx, claimsErr
} else {
ctx = context.WithValue(ctx, "claims", "")
}
}

View File

@@ -3,6 +3,8 @@ package server
import (
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
@@ -13,6 +15,7 @@ import (
"github.com/golang-jwt/jwt/v4"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/metadata"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
@@ -434,6 +437,392 @@ func TestAuthenticate(t *testing.T) {
}
}
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")
switch r.RequestURI {
case "/api/dex/.well-known/openid-configuration":
_, err := io.WriteString(w, fmt.Sprintf(`
{
"issuer": "%[1]s/api/dex",
"authorization_endpoint": "%[1]s/api/dex/auth",
"token_endpoint": "%[1]s/api/dex/token",
"jwks_uri": "%[1]s/api/dex/keys",
"userinfo_endpoint": "%[1]s/api/dex/userinfo",
"device_authorization_endpoint": "%[1]s/api/dex/device/code",
"grant_types_supported": [
"authorization_code",
"refresh_token",
"urn:ietf:params:oauth:grant-type:device_code"
],
"response_types_supported": [
"code"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS256", "HS256"
],
"code_challenge_methods_supported": [
"S256",
"plain"
],
"scopes_supported": [
"openid",
"email",
"groups",
"profile",
"offline_access"
],
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post"
],
"claims_supported": [
"iss",
"sub",
"aud",
"iat",
"exp",
"email",
"email_verified",
"locale",
"name",
"preferred_username",
"at_hash"
]
}`, url))
if err != nil {
t.Fail()
}
default:
w.WriteHeader(404)
}
}
}
func getTestServer(t *testing.T, anonymousEnabled bool, withFakeSSO bool) (argocd *ArgoCDServer, dexURL string) {
cm := test.NewFakeConfigMap()
if anonymousEnabled {
cm.Data["users.anonymous.enabled"] = "true"
}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
return // Start with a placeholder. We need the server URL before setting up the real handler.
}))
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
dexMockHandler(t, ts.URL)(w, r)
})
if withFakeSSO {
cm.Data["url"] = ts.URL
cm.Data["dex.config"] = `
connectors:
# OIDC
- type: OIDC
id: oidc
name: OIDC
config:
issuer: https://auth.example.gom
clientID: test-client
clientSecret: $dex.oidc.clientSecret`
}
secret := test.NewFakeSecret()
kubeclientset := fake.NewSimpleClientset(cm, secret)
appClientSet := apps.NewSimpleClientset()
argoCDOpts := ArgoCDServerOpts{
Namespace: test.FakeArgoCDNamespace,
KubeClientset: kubeclientset,
AppClientset: appClientSet,
}
if withFakeSSO {
argoCDOpts.DexServerAddr = ts.URL
}
argocd = NewServer(context.Background(), argoCDOpts)
return argocd, ts.URL
}
func TestAuthenticate_3rd_party_JWTs(t *testing.T) {
type testData struct {
test string
anonymousEnabled bool
claims jwt.RegisteredClaims
expectedErrorContains string
expectedClaims interface{}
}
var tests = []testData{
{
test: "anonymous disabled, no audience",
anonymousEnabled: false,
claims: jwt.RegisteredClaims{},
expectedErrorContains: "no audience found in the token",
expectedClaims: nil,
},
{
test: "anonymous enabled, no audience",
anonymousEnabled: true,
claims: jwt.RegisteredClaims{},
expectedErrorContains: "",
expectedClaims: "",
},
{
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",
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))},
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",
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())},
expectedErrorContains: "",
expectedClaims: "",
},
}
for _, testData := range tests {
testDataCopy := testData
t.Run(testDataCopy.test, func(t *testing.T) {
t.Parallel()
// 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)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, testDataCopy.claims)
tokenString, err := token.SignedString([]byte("key"))
require.NoError(t, err)
ctx = metadata.NewIncomingContext(context.Background(), metadata.Pairs(apiclient.MetaDataTokenKey, tokenString))
ctx, err = argocd.Authenticate(ctx)
claims := ctx.Value("claims")
if testDataCopy.expectedClaims == nil {
assert.Nil(t, claims)
} else {
assert.Equal(t, testDataCopy.expectedClaims, claims)
}
if testDataCopy.expectedErrorContains != "" {
assert.ErrorContains(t, err, testDataCopy.expectedErrorContains, "Authenticate should have thrown an error and blocked the request")
} else {
assert.NoError(t, err)
}
})
}
}
func TestAuthenticate_no_request_metadata(t *testing.T) {
type testData struct {
test string
anonymousEnabled bool
expectedErrorContains string
expectedClaims interface{}
}
var tests = []testData{
{
test: "anonymous disabled",
anonymousEnabled: false,
expectedErrorContains: "no session information",
expectedClaims: nil,
},
{
test: "anonymous enabled",
anonymousEnabled: true,
expectedErrorContains: "",
expectedClaims: "",
},
}
for _, testData := range tests {
testDataCopy := testData
t.Run(testDataCopy.test, func(t *testing.T) {
t.Parallel()
argocd, _ := getTestServer(t, testDataCopy.anonymousEnabled, true)
ctx := context.Background()
ctx, err := argocd.Authenticate(ctx)
claims := ctx.Value("claims")
assert.Equal(t, testDataCopy.expectedClaims, claims)
if testDataCopy.expectedErrorContains != "" {
assert.ErrorContains(t, err, testDataCopy.expectedErrorContains, "Authenticate should have thrown an error and blocked the request")
} else {
assert.NoError(t, err)
}
})
}
}
func TestAuthenticate_no_SSO(t *testing.T) {
type testData struct {
test string
anonymousEnabled bool
expectedErrorMessage string
expectedClaims interface{}
}
var tests = []testData{
{
test: "anonymous disabled",
anonymousEnabled: false,
expectedErrorMessage: "SSO is not configured",
expectedClaims: nil,
},
{
test: "anonymous enabled",
anonymousEnabled: true,
expectedErrorMessage: "",
expectedClaims: "",
},
}
for _, testData := range tests {
testDataCopy := testData
t.Run(testDataCopy.test, func(t *testing.T) {
t.Parallel()
// Must be declared here to avoid race.
ctx := context.Background() //nolint:ineffassign,staticcheck
argocd, dexURL := getTestServer(t, testDataCopy.anonymousEnabled, false)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{Issuer: fmt.Sprintf("%s/api/dex", dexURL)})
tokenString, err := token.SignedString([]byte("key"))
require.NoError(t, err)
ctx = metadata.NewIncomingContext(context.Background(), metadata.Pairs(apiclient.MetaDataTokenKey, tokenString))
ctx, err = argocd.Authenticate(ctx)
claims := ctx.Value("claims")
assert.Equal(t, testDataCopy.expectedClaims, claims)
if testDataCopy.expectedErrorMessage != "" {
assert.ErrorContains(t, err, testDataCopy.expectedErrorMessage, "Authenticate should have thrown an error and blocked the request")
} else {
assert.NoError(t, err)
}
})
}
}
func TestAuthenticate_bad_request_metadata(t *testing.T) {
type testData struct {
test string
anonymousEnabled bool
metadata metadata.MD
expectedErrorMessage string
expectedClaims interface{}
}
var tests = []testData{
{
test: "anonymous disabled, empty metadata",
anonymousEnabled: false,
metadata: metadata.MD{},
expectedErrorMessage: "no session information",
expectedClaims: nil,
},
{
test: "anonymous enabled, empty metadata",
anonymousEnabled: true,
metadata: metadata.MD{},
expectedErrorMessage: "",
expectedClaims: "",
},
{
test: "anonymous disabled, empty tokens",
anonymousEnabled: false,
metadata: metadata.MD{apiclient.MetaDataTokenKey: []string{}},
expectedErrorMessage: "no session information",
expectedClaims: nil,
},
{
test: "anonymous enabled, empty tokens",
anonymousEnabled: true,
metadata: metadata.MD{apiclient.MetaDataTokenKey: []string{}},
expectedErrorMessage: "",
expectedClaims: "",
},
{
test: "anonymous disabled, bad tokens",
anonymousEnabled: false,
metadata: metadata.Pairs(apiclient.MetaDataTokenKey, "bad"),
expectedErrorMessage: "token contains an invalid number of segments",
expectedClaims: nil,
},
{
test: "anonymous enabled, bad tokens",
anonymousEnabled: true,
metadata: metadata.Pairs(apiclient.MetaDataTokenKey, "bad"),
expectedErrorMessage: "",
expectedClaims: "",
},
{
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",
expectedClaims: nil,
},
{
test: "anonymous enabled, bad auth header",
anonymousEnabled: true,
metadata: metadata.MD{"authorization": []string{"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiJ9.TGGTTHuuGpEU8WgobXxkrBtW3NiR3dgw5LR-1DEW3BQ"}},
expectedErrorMessage: "",
expectedClaims: "",
},
{
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",
expectedClaims: nil,
},
{
test: "anonymous enabled, bad auth cookie",
anonymousEnabled: true,
metadata: metadata.MD{"grpcgateway-cookie": []string{"argocd.token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiJ9.TGGTTHuuGpEU8WgobXxkrBtW3NiR3dgw5LR-1DEW3BQ"}},
expectedErrorMessage: "",
expectedClaims: "",
},
}
for _, testData := range tests {
testDataCopy := testData
t.Run(testDataCopy.test, func(t *testing.T) {
t.Parallel()
// Must be declared here to avoid race.
ctx := context.Background() //nolint:ineffassign,staticcheck
argocd, _ := getTestServer(t, testDataCopy.anonymousEnabled, true)
ctx = metadata.NewIncomingContext(context.Background(), testDataCopy.metadata)
ctx, err := argocd.Authenticate(ctx)
claims := ctx.Value("claims")
assert.Equal(t, testDataCopy.expectedClaims, claims)
if testDataCopy.expectedErrorMessage != "" {
assert.ErrorContains(t, err, testDataCopy.expectedErrorMessage, "Authenticate should have thrown an error and blocked the request")
} else {
assert.NoError(t, err)
}
})
}
}
func Test_getToken(t *testing.T) {
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
t.Run("Empty", func(t *testing.T) {

View File

@@ -6,7 +6,7 @@ FROM golang:1.18 as golang
FROM registry:2.8 as registry
FROM ubuntu:21.10
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install --fix-missing -y \
@@ -61,6 +61,11 @@ COPY ./test/fixture/testrepos/ssh_host_*_key* /etc/ssh/
# Copy redis binaries to the image
COPY --from=redis /usr/local/bin/* /usr/local/bin/
# Copy redis dependencies/shared libraries
# Ubuntu 22.04+ has moved to OpenSSL3 and no longer provides these libraries
COPY --from=redis /usr/lib/x86_64-linux-gnu/libssl.so.1.1 /usr/lib/x86_64-linux-gnu/
COPY --from=redis /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 /usr/lib/x86_64-linux-gnu/
# Copy registry binaries to the image
COPY --from=registry /bin/registry /usr/local/bin/
COPY --from=registry /etc/docker/registry/config.yml /etc/docker/registry/config.yml

View File

@@ -184,8 +184,6 @@ func TestClusterURLInRestAPI(t *testing.T) {
}
func TestClusterDeleteDenied(t *testing.T) {
EnsureCleanState(t)
accountFixture.Given(t).
Name("test").
When().

View File

@@ -102,7 +102,7 @@ func TestCustomToolWithEnv(t *testing.T) {
Name: Name(),
Generate: Command{
Command: []string{"sh", "-c"},
Args: []string{`echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"Foo\": \"$FOO\", \"KubeVersion\": \"$KUBE_VERSION\", \"KubeApiVersion\": \"$KUBE_API_VERSIONS\",\"Bar\": \"baz\"}}}"`},
Args: []string{`echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"Foo\": \"$ARGOCD_ENV_FOO\", \"KubeVersion\": \"$KUBE_VERSION\", \"KubeApiVersion\": \"$KUBE_API_VERSIONS\",\"Bar\": \"baz\"}}}"`},
},
},
).
@@ -162,7 +162,7 @@ func TestCustomToolSyncAndDiffLocal(t *testing.T) {
Name: Name(),
Generate: Command{
Command: []string{"sh", "-c"},
Args: []string{`echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"Foo\": \"$FOO\", \"KubeVersion\": \"$KUBE_VERSION\", \"KubeApiVersion\": \"$KUBE_API_VERSIONS\",\"Bar\": \"baz\"}}}"`},
Args: []string{`echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"Foo\": \"$ARGOCD_ENV_FOO\", \"KubeVersion\": \"$KUBE_VERSION\", \"KubeApiVersion\": \"$KUBE_API_VERSIONS\",\"Bar\": \"baz\"}}}"`},
},
},
).

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"log"
"github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/clusterauth"
"k8s.io/client-go/kubernetes"
@@ -81,7 +82,7 @@ func (a *Actions) CreateWithRBAC(args ...string) *Actions {
}
client := kubernetes.NewForConfigOrDie(conf)
_, err = clusterauth.InstallClusterManagerRBAC(client, "kube-system", []string{})
_, err = clusterauth.InstallClusterManagerRBAC(client, "kube-system", []string{}, common.BearerTokenTimeout)
if err != nil {
a.lastError = err
return a

View File

@@ -590,10 +590,10 @@ func TestGetVirtualProjectMatch(t *testing.T) {
//App trying to sync a resource which is not blacked listed anywhere
_, err = fixture.RunCli("app", "sync", fixture.Name(), "--resource", "apps:Deployment:guestbook-ui", "--timeout", fmt.Sprintf("%v", 10))
assert.Error(t, err)
assert.Contains(t, err.Error(), "Blocked by sync window")
assert.Contains(t, err.Error(), "blocked by sync window")
//app trying to sync a resource which is black listed by global project
_, err = fixture.RunCli("app", "sync", fixture.Name(), "--resource", ":Service:guestbook-ui", "--timeout", fmt.Sprintf("%v", 10))
assert.Contains(t, err.Error(), "Blocked by sync window")
assert.Contains(t, err.Error(), "blocked by sync window")
}

View File

@@ -3,7 +3,7 @@ FROM golang:1.18 AS go
RUN go install github.com/mattn/goreman@latest && \
go install github.com/kisielk/godepgraph@latest
FROM ubuntu:21.10
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install --no-install-recommends -y \
@@ -23,8 +23,6 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
WORKDIR /etc/certs
# These are required for running end-to-end tests
COPY ./test/fixture/testrepos/id_rsa.pub /root/.ssh/authorized_keys
COPY ./test/fixture/testrepos/nginx.conf /etc/nginx/nginx.conf

View File

@@ -145,14 +145,15 @@ $num-stats: 2;
background-color: $argo-color-gray-6;
}
}
&__star-icon {
&__new-pod-icon {
background: none;
color: #ffce25;
color: #FFCE25;
display: block;
left: 20px;
margin: 0px;
position: absolute;
top: -5px;
top: -4px;
font-size: 10px;
}
&__stat-tooltip {
text-align: left;

View File

@@ -219,7 +219,7 @@ export class PodView extends React.Component<PodViewProps> {
key={pod.metadata.name}>
<div style={{position: 'relative'}}>
{isYoungerThanXMinutes(pod, 30) && (
<i className='fas fa-star pod-view__node__pod pod-view__node__pod__star-icon' />
<i className='fas fa-circle pod-view__node__pod pod-view__node__pod__new-pod-icon' />
)}
<div className={`pod-view__node__pod pod-view__node__pod--${pod.health.toLowerCase()}`}>
<PodHealthIcon state={{status: pod.health, message: ''}} />

View File

@@ -179,7 +179,6 @@
padding-bottom: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
}

View File

@@ -230,7 +230,7 @@ const FavoriteFilter = (props: AppFilterProps) => {
}}
/>
<div style={{marginRight: '5px'}}>
<i style={{color: '#1FBDD0'}} className='fas fa-star' />
<i style={{color: '#FFCE25'}} className='fas fa-star' />
</div>
<div className='filter__item__label'>Favorites Only</div>
</div>

View File

@@ -73,11 +73,11 @@ export const ApplicationsTable = (props: {
services.viewPreferences.updatePreferences({appList: {...pref.appList, favoritesAppList: favList}});
}}>
<i
className={'fas fa-star'}
className={favList?.includes(app.metadata.name) ? 'fas fa-star' : 'far fa-star'}
style={{
cursor: 'pointer',
marginRight: '7px',
color: favList?.includes(app.metadata.name) ? '#1FBDD0' : 'grey'
color: favList?.includes(app.metadata.name) ? '#FFCE25' : '#8fa4b1'
}}
/>
</button>

View File

@@ -117,29 +117,51 @@ export const ApplicationTiles = ({applications, syncApplication, refreshApplicat
}`}>
<div className='row' onClick={e => ctx.navigation.goto(`/applications/${app.metadata.name}`, {view: pref.appDetails.view}, {event: e})}>
<div className={`columns small-12 applications-list__info qe-applications-list-${app.metadata.name}`}>
<div className='applications-list__external-link'>
<ApplicationURLs urls={AppUtils.getExternalUrls(app.metadata.annotations, app.status.summary.externalURLs)} />
<Tooltip content={favList?.includes(app.metadata.name) ? 'Remove Favorite' : 'Add Favorite'}>
<button
className='large-text-height'
onClick={e => {
e.stopPropagation();
favList?.includes(app.metadata.name)
? favList.splice(favList.indexOf(app.metadata.name), 1)
: favList.push(app.metadata.name);
services.viewPreferences.updatePreferences({appList: {...pref.appList, favoritesAppList: favList}});
}}>
<i
className={'fas fa-star fa-lg'}
style={{cursor: 'pointer', marginLeft: '7px', color: favList?.includes(app.metadata.name) ? '#1FBDD0' : 'grey'}}
/>
</button>
</Tooltip>
</div>
<div className='row'>
<div className='columns small-12'>
<div
className={
AppUtils.getExternalUrls(app.metadata.annotations, app.status.summary.externalURLs)?.length > 0
? 'columns small-10'
: 'columns small-11'
}>
<i className={'icon argo-icon-' + (app.spec.source.chart != null ? 'helm' : 'git')} />
<span className='applications-list__title'>{app.metadata.name}</span>
{app.metadata.name.length > 30 ? (
<Tooltip content={app.metadata.name}>
<span className='applications-list__title'>{app.metadata.name}</span>
</Tooltip>
) : (
<span className='applications-list__title'>{app.metadata.name}</span>
)}
</div>
<div
className={
AppUtils.getExternalUrls(app.metadata.annotations, app.status.summary.externalURLs)?.length > 0
? 'columns small-2'
: 'columns small-1'
}>
<div className='applications-list__external-link'>
<ApplicationURLs urls={AppUtils.getExternalUrls(app.metadata.annotations, app.status.summary.externalURLs)} />
<Tooltip content={favList?.includes(app.metadata.name) ? 'Remove Favorite' : 'Add Favorite'}>
<button
className='large-text-height'
onClick={e => {
e.stopPropagation();
favList?.includes(app.metadata.name)
? favList.splice(favList.indexOf(app.metadata.name), 1)
: favList.push(app.metadata.name);
services.viewPreferences.updatePreferences({appList: {...pref.appList, favoritesAppList: favList}});
}}>
<i
className={favList?.includes(app.metadata.name) ? 'fas fa-star fa-lg' : 'far fa-star fa-lg'}
style={{
cursor: 'pointer',
marginLeft: '7px',
color: favList?.includes(app.metadata.name) ? '#FFCE25' : '#8fa4b1'
}}
/>
</button>
</Tooltip>
</div>
</div>
</div>
<div className='row'>

View File

@@ -12,6 +12,7 @@ import {Context} from '../../../shared/context';
import {ErrorNotification, NotificationType} from 'argo-ui';
export interface PodTerminalViewerProps {
applicationName: string;
projectName: string;
selectedNode: models.ResourceNode;
podState: models.State;
}
@@ -22,7 +23,7 @@ export interface ShellFrame {
cols?: number;
}
export const PodTerminalViewer: React.FC<PodTerminalViewerProps> = ({selectedNode, applicationName, podState}) => {
export const PodTerminalViewer: React.FC<PodTerminalViewerProps> = ({selectedNode, applicationName, projectName, podState}) => {
const terminalRef = React.useRef(null);
const appContext = React.useContext(Context); // used to show toast
const fitAddon = new FitAddon();
@@ -144,7 +145,7 @@ export const PodTerminalViewer: React.FC<PodTerminalViewerProps> = ({selectedNod
`${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/terminal?pod=${name}&container=${AppUtils.getContainerName(
podState,
activeContainer
)}&appName=${applicationName}&namespace=${namespace}`
)}&appName=${applicationName}&projectName=${projectName}&namespace=${namespace}`
);
webSocket.onopen = onConnectionOpen;
webSocket.onclose = onConnectionClose;

View File

@@ -117,7 +117,9 @@ export const ResourceDetails = (props: ResourceDetailsProps) => {
key: 'exec',
icon: 'fa fa-terminal',
title: 'Terminal',
content: <PodTerminalViewer applicationName={application.metadata.name} podState={podState} selectedNode={selectedNode} />
content: (
<PodTerminalViewer applicationName={application.metadata.name} projectName={application.spec.project} podState={podState} selectedNode={selectedNode} />
)
}
]);
}

View File

@@ -20,7 +20,7 @@ interface State {
loginError: string;
loginInProgress: boolean;
returnUrl: string;
ssoLoginError: string;
hasSsoLoginError: boolean;
}
export class Login extends React.Component<RouteComponentProps<{}>, State> {
@@ -31,13 +31,13 @@ export class Login extends React.Component<RouteComponentProps<{}>, State> {
public static getDerivedStateFromProps(props: RouteComponentProps<{}>): Partial<State> {
const search = new URLSearchParams(props.history.location.search);
const returnUrl = search.get('return_url') || '';
const ssoLoginError = search.get('sso_error') || '';
return {ssoLoginError, returnUrl};
const hasSsoLoginError = search.get('has_sso_error') === 'true';
return {hasSsoLoginError, returnUrl};
}
constructor(props: RouteComponentProps<{}>) {
super(props);
this.state = {authSettings: null, loginError: null, returnUrl: null, ssoLoginError: null, loginInProgress: false};
this.state = {authSettings: null, loginError: null, returnUrl: null, hasSsoLoginError: false, loginInProgress: false};
}
public async componentDidMount() {
@@ -69,7 +69,7 @@ export class Login extends React.Component<RouteComponentProps<{}>, State> {
)}
</button>
</a>
{this.state.ssoLoginError && <div className='argo-form-row__error-msg'>{this.state.ssoLoginError}</div>}
{this.state.hasSsoLoginError && <div className='argo-form-row__error-msg'>Login failed.</div>}
{authSettings && !authSettings.userLoginsDisabled && (
<div className='login__saml-separator'>
<span>or</span>

View File

@@ -22,7 +22,7 @@ export class ApplicationsService {
public list(projects: string[], options?: QueryOptions): Promise<models.ApplicationList> {
return requests
.get('/applications')
.query({project: projects, ...optionsToSearch(options)})
.query({projects, ...optionsToSearch(options)})
.then(res => res.body as models.ApplicationList)
.then(list => {
list.items = (list.items || []).map(app => this.parseAppFields(app));

View File

@@ -10,12 +10,7 @@ export class ClustersService {
}
public get(url: string, name: string): Promise<models.Cluster> {
let queryName = '';
if (url === undefined) {
url = '';
queryName = `?name=${name}`;
}
const requestUrl = `/clusters/${encodeURIComponent(url)}` + queryName;
const requestUrl = `/clusters/${url ? encodeURIComponent(url) : name}?id.type=${url ? 'url' : 'name'}`;
return requests.get(requestUrl).then(res => res.body as models.Cluster);
}

View File

@@ -33,7 +33,7 @@ func Discover(ctx context.Context, repoPath string, enableGenerateManifests map[
apps := make(map[string]string)
// Check if it is CMP
conn, _, err := DetectConfigManagementPlugin(ctx, repoPath)
conn, _, err := DetectConfigManagementPlugin(ctx, repoPath, []string{})
if err == nil {
// Found CMP
io.Close(conn)
@@ -82,7 +82,7 @@ func AppType(ctx context.Context, path string, enableGenerateManifests map[strin
// 3. check isSupported(path)?
// 4.a if no then close connection
// 4.b if yes then return conn for detected plugin
func DetectConfigManagementPlugin(ctx context.Context, repoPath string) (io.Closer, pluginclient.ConfigManagementPluginServiceClient, error) {
func DetectConfigManagementPlugin(ctx context.Context, repoPath string, env []string) (io.Closer, pluginclient.ConfigManagementPluginServiceClient, error) {
var conn io.Closer
var cmpClient pluginclient.ConfigManagementPluginServiceClient
@@ -106,7 +106,7 @@ func DetectConfigManagementPlugin(ctx context.Context, repoPath string) (io.Clos
continue
}
isSupported, err := matchRepositoryCMP(ctx, repoPath, cmpClient)
isSupported, err := matchRepositoryCMP(ctx, repoPath, cmpClient, env)
if err != nil {
log.Errorf("repository %s is not the match because %v", repoPath, err)
continue
@@ -131,13 +131,13 @@ func DetectConfigManagementPlugin(ctx context.Context, repoPath string) (io.Clos
// matchRepositoryCMP will send the repoPath to the cmp-server. The cmp-server will
// inspect the files and return true if the repo is supported for manifest generation.
// Will return false otherwise.
func matchRepositoryCMP(ctx context.Context, repoPath string, client pluginclient.ConfigManagementPluginServiceClient) (bool, error) {
func matchRepositoryCMP(ctx context.Context, repoPath string, client pluginclient.ConfigManagementPluginServiceClient, env []string) (bool, error) {
matchRepoStream, err := client.MatchRepository(ctx, grpc_retry.Disable())
if err != nil {
return false, fmt.Errorf("error getting stream client: %s", err)
}
err = cmp.SendRepoStream(ctx, repoPath, repoPath, matchRepoStream, []string{})
err = cmp.SendRepoStream(ctx, repoPath, repoPath, matchRepoStream, env)
if err != nil {
return false, fmt.Errorf("error sending stream: %s", err)
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/argo/managedfields"
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
k8smanagedfields "k8s.io/apimachinery/pkg/util/managedfields"
"github.com/argoproj/gitops-engine/pkg/diff"
"github.com/argoproj/gitops-engine/pkg/utils/kube"
@@ -73,6 +74,12 @@ func (b *DiffConfigBuilder) WithLogger(l logr.Logger) *DiffConfigBuilder {
return b
}
// WithGVKParser sets the gvkParser in the diff config.
func (b *DiffConfigBuilder) WithGVKParser(parser *k8smanagedfields.GvkParser) *DiffConfigBuilder {
b.diffConfig.gvkParser = parser
return b
}
// Build will first validate the current state of the diff config and return the
// DiffConfig implementation if no errors are found. Will return nil and the error
// details otherwise.
@@ -108,6 +115,9 @@ type DiffConfig interface {
IgnoreAggregatedRoles() bool
// Logger used during the diff
Logger() *logr.Logger
// GVKParser returns a parser able to build a TypedValue used in
// structured merge diffs.
GVKParser() *k8smanagedfields.GvkParser
}
// diffConfig defines the configurations used while applying diffs.
@@ -121,6 +131,7 @@ type diffConfig struct {
stateCache *appstatecache.Cache
ignoreAggregatedRoles bool
logger *logr.Logger
gvkParser *k8smanagedfields.GvkParser
}
func (c *diffConfig) Ignores() []v1alpha1.ResourceIgnoreDifferences {
@@ -150,6 +161,9 @@ func (c *diffConfig) IgnoreAggregatedRoles() bool {
func (c *diffConfig) Logger() *logr.Logger {
return c.logger
}
func (c *diffConfig) GVKParser() *k8smanagedfields.GvkParser {
return c.gvkParser
}
// Validate will check the current state of this diffConfig and return
// error if it finds any required configuration missing.
@@ -309,8 +323,9 @@ func preDiffNormalize(lives, targets []*unstructured.Unstructured, diffConfig Di
idc := NewIgnoreDiffConfig(diffConfig.Ignores(), diffConfig.Overrides())
ok, ignoreDiff := idc.HasIgnoreDifference(gvk.Group, gvk.Kind, target.GetName(), target.GetNamespace())
if ok && len(ignoreDiff.ManagedFieldsManagers) > 0 {
pt := managedfields.ResolveParseableType(gvk, diffConfig.GVKParser())
var err error
live, target, err = managedfields.Normalize(live, target, ignoreDiff.ManagedFieldsManagers)
live, target, err = managedfields.Normalize(live, target, ignoreDiff.ManagedFieldsManagers, pt)
if err != nil {
return nil, err
}

View File

@@ -2,9 +2,14 @@ package managedfields
import (
"bytes"
"fmt"
"reflect"
"sync"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
k8smanagedfields "k8s.io/apimachinery/pkg/util/managedfields"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/typed"
)
@@ -13,9 +18,11 @@ import (
// a field that belongs to one of the trustedManagers it will remove
// that field from both live and config objects and return the normalized
// objects in this order. This function won't modify the live and config
// parameters. It is a no-op if no trustedManagers is provided. It is also
// a no-op if live or config are nil.
func Normalize(live, config *unstructured.Unstructured, trustedManagers []string) (*unstructured.Unstructured, *unstructured.Unstructured, error) {
// parameters. If pt is nil, the normalization will use a deduced parseable
// type which means that lists and maps are manipulated atomically.
// It is a no-op if no trustedManagers is provided. It is also a no-op if
// live or config are nil.
func Normalize(live, config *unstructured.Unstructured, trustedManagers []string, pt *typed.ParseableType) (*unstructured.Unstructured, *unstructured.Unstructured, error) {
if len(trustedManagers) == 0 {
return nil, nil, nil
}
@@ -25,68 +32,87 @@ func Normalize(live, config *unstructured.Unstructured, trustedManagers []string
liveCopy := live.DeepCopy()
configCopy := config.DeepCopy()
comparison, err := Compare(liveCopy, configCopy)
results, err := newTypedResults(liveCopy, configCopy, pt)
if err != nil {
return nil, nil, err
return nil, nil, fmt.Errorf("error building typed results: %s", err)
}
normalized := false
for _, mf := range live.GetManagedFields() {
if trustedManager(mf.Manager, trustedManagers) {
err := normalize(liveCopy, configCopy, mf, comparison.Modified)
err := normalize(mf, results)
if err != nil {
return nil, nil, err
return nil, nil, fmt.Errorf("error normalizing manager %s: %s", mf.Manager, err)
}
normalized = true
}
}
return liveCopy, configCopy, nil
if !normalized {
return liveCopy, configCopy, nil
}
lvu := results.live.AsValue().Unstructured()
l, ok := lvu.(map[string]interface{})
if !ok {
return nil, nil, fmt.Errorf("error converting live typedValue: expected map got %T", lvu)
}
normLive := &unstructured.Unstructured{Object: l}
cvu := results.config.AsValue().Unstructured()
c, ok := cvu.(map[string]interface{})
if !ok {
return nil, nil, fmt.Errorf("error converting config typedValue: expected map got %T", cvu)
}
normConfig := &unstructured.Unstructured{Object: c}
return normLive, normConfig, nil
}
// normalize will check if the modified set has fields that are present
// in the managed fields entry. If so, it will remove the fields from
// the live and config objects so it is ignored in diffs.
func normalize(live, config *unstructured.Unstructured, mf v1.ManagedFieldsEntry, modified *fieldpath.Set) error {
liveSet := &fieldpath.Set{}
err := liveSet.FromJSON(bytes.NewReader(mf.FieldsV1.Raw))
func normalize(mf v1.ManagedFieldsEntry, tr *typedResults) error {
mfs := &fieldpath.Set{}
err := mfs.FromJSON(bytes.NewReader(mf.FieldsV1.Raw))
if err != nil {
return err
}
intersect := liveSet.Intersection(modified)
if !intersect.Empty() {
intersect.Iterate(func(p fieldpath.Path) {
fields := PathToNestedFields(p)
unstructured.RemoveNestedField(config.Object, fields...)
unstructured.RemoveNestedField(live.Object, fields...)
})
intersect := mfs.Intersection(tr.comparison.Modified)
if intersect.Empty() {
return nil
}
tr.live = tr.live.RemoveItems(intersect)
tr.config = tr.config.RemoveItems(intersect)
return nil
}
// Compare will compare the live and the config state and returned a typed.Comparison
// as a result.
func Compare(live, config *unstructured.Unstructured) (*typed.Comparison, error) {
typedLive, err := typed.DeducedParseableType.FromUnstructured(live.Object)
if err != nil {
return nil, err
}
typedConfig, err := typed.DeducedParseableType.FromUnstructured(config.Object)
if err != nil {
return nil, err
}
return typedLive.Compare(typedConfig)
type typedResults struct {
live *typed.TypedValue
config *typed.TypedValue
comparison *typed.Comparison
}
// PathToNestedFields will convert a path into a slice of field names so it
// can be used in unstructured nested fields operations.
func PathToNestedFields(path fieldpath.Path) []string {
fields := []string{}
for _, element := range path {
if element.FieldName != nil {
fields = append(fields, *element.FieldName)
}
// newTypedResults will convert live and config into a TypedValue using the given pt
// and compare them. Returns a typedResults with the coverted types and the comparison.
// If pt is nil, will use the DeducedParseableType.
func newTypedResults(live, config *unstructured.Unstructured, pt *typed.ParseableType) (*typedResults, error) {
typedLive, err := pt.FromUnstructured(live.Object)
if err != nil {
return nil, fmt.Errorf("error creating typedLive: %s", err)
}
return fields
typedConfig, err := pt.FromUnstructured(config.Object)
if err != nil {
return nil, fmt.Errorf("error creating typedConfig: %s", err)
}
comparison, err := typedLive.Compare(typedConfig)
if err != nil {
return nil, fmt.Errorf("error comparing typed resources: %s", err)
}
return &typedResults{
live: typedLive,
config: typedConfig,
comparison: comparison,
}, nil
}
// trustedManager will return true if trustedManagers contains curManager.
@@ -99,3 +125,60 @@ func trustedManager(curManager string, trustedManagers []string) bool {
}
return false
}
func ResolveParseableType(gvk schema.GroupVersionKind, parser *k8smanagedfields.GvkParser) *typed.ParseableType {
if parser == nil {
return &typed.DeducedParseableType
}
pt := resolverFromStaticParser(gvk, parser)
if pt == nil {
return parser.Type(gvk)
}
return pt
}
func resolverFromStaticParser(gvk schema.GroupVersionKind, parser *k8smanagedfields.GvkParser) *typed.ParseableType {
gvkNameMap := getGvkMap(parser)
name := gvkNameMap[gvk]
p := StaticParser()
if p == nil || name == "" {
return nil
}
pt := p.Type(name)
if pt.IsValid() {
return &pt
}
return nil
}
var gvkMap map[schema.GroupVersionKind]string
var extractOnce sync.Once
func getGvkMap(parser *k8smanagedfields.GvkParser) map[schema.GroupVersionKind]string {
extractOnce.Do(func() {
gvkMap = extractGvkMap(parser)
})
return gvkMap
}
func extractGvkMap(parser *k8smanagedfields.GvkParser) map[schema.GroupVersionKind]string {
results := make(map[schema.GroupVersionKind]string)
value := reflect.ValueOf(parser)
gvkValue := reflect.Indirect(value).FieldByName("gvks")
iter := gvkValue.MapRange()
for iter.Next() {
group := iter.Key().FieldByName("Group").String()
version := iter.Key().FieldByName("Version").String()
kind := iter.Key().FieldByName("Kind").String()
gvk := schema.GroupVersionKind{
Group: group,
Version: version,
Kind: kind,
}
name := iter.Value().String()
results[gvk] = name
}
return results
}

View File

@@ -5,24 +5,32 @@ import (
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
arv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/argoproj/argo-cd/v2/util/argo/managedfields"
"github.com/argoproj/argo-cd/v2/util/argo/testdata"
)
func TestNormalize(t *testing.T) {
parser := managedfields.StaticParser()
t.Run("will remove conflicting fields if managed by trusted managers", func(t *testing.T) {
// given
desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml)
liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml)
trustedManagers := []string{"kube-controller-manager", "revision-history-manager"}
pt := parser.Type("io.k8s.api.apps.v1.Deployment")
// when
liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, trustedManagers)
liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, trustedManagers, &pt)
// then
assert.NoError(t, err)
require.NoError(t, err)
require.NotNil(t, liveResult)
require.NotNil(t, desiredResult)
desiredReplicas, ok, err := unstructured.NestedFloat64(desiredResult.Object, "spec", "replicas")
assert.False(t, ok)
assert.NoError(t, err)
@@ -44,9 +52,10 @@ func TestNormalize(t *testing.T) {
desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml)
liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml)
trustedManagers := []string{"another-manager"}
pt := parser.Type("io.k8s.api.apps.v1.Deployment")
// when
liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, trustedManagers)
liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, trustedManagers, &pt)
// then
assert.NoError(t, err)
@@ -59,9 +68,10 @@ func TestNormalize(t *testing.T) {
// given
desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml)
trustedManagers := []string{"kube-controller-manager"}
pt := parser.Type("io.k8s.api.apps.v1.Deployment")
// when
liveResult, desiredResult, err := managedfields.Normalize(nil, desiredState, trustedManagers)
liveResult, desiredResult, err := managedfields.Normalize(nil, desiredState, trustedManagers, &pt)
// then
assert.NoError(t, err)
@@ -75,9 +85,10 @@ func TestNormalize(t *testing.T) {
// given
liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml)
trustedManagers := []string{"kube-controller-manager"}
pt := parser.Type("io.k8s.api.apps.v1.Deployment")
// when
liveResult, desiredResult, err := managedfields.Normalize(liveState, nil, trustedManagers)
liveResult, desiredResult, err := managedfields.Normalize(liveState, nil, trustedManagers, &pt)
// then
assert.NoError(t, err)
@@ -90,9 +101,10 @@ func TestNormalize(t *testing.T) {
// given
desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml)
liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml)
pt := parser.Type("io.k8s.api.apps.v1.Deployment")
// when
liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, []string{})
liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, []string{}, &pt)
// then
assert.NoError(t, err)
@@ -103,6 +115,33 @@ func TestNormalize(t *testing.T) {
validateNestedFloat64(t, float64(2), liveState, "spec", "replicas")
validateNestedFloat64(t, float64(3), liveState, "spec", "revisionHistoryLimit")
})
t.Run("will normalize successfully inside a list", func(t *testing.T) {
// given
desiredState := StrToUnstructured(testdata.DesiredValidatingWebhookYaml)
liveState := StrToUnstructured(testdata.LiveValidatingWebhookYaml)
trustedManagers := []string{"external-secrets"}
pt := parser.Type("io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration")
// when
liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, trustedManagers, &pt)
// then
require.NoError(t, err)
require.NotNil(t, liveResult)
require.NotNil(t, desiredResult)
var vwcLive arv1.ValidatingWebhookConfiguration
err = runtime.DefaultUnstructuredConverter.FromUnstructured(liveResult.Object, &vwcLive)
require.NoError(t, err)
assert.Equal(t, 1, len(vwcLive.Webhooks))
assert.Equal(t, "", string(vwcLive.Webhooks[0].ClientConfig.CABundle))
var vwcConfig arv1.ValidatingWebhookConfiguration
err = runtime.DefaultUnstructuredConverter.FromUnstructured(desiredResult.Object, &vwcConfig)
require.NoError(t, err)
assert.Equal(t, 1, len(vwcConfig.Webhooks))
assert.Equal(t, "", string(vwcConfig.Webhooks[0].ClientConfig.CABundle))
})
}
func validateNestedFloat64(t *testing.T, expected float64, obj *unstructured.Unstructured, fields ...string) {

File diff suppressed because it is too large Load Diff

View File

@@ -8,4 +8,10 @@ var (
//go:embed desired_deployment.yaml
DesiredDeploymentYaml string
//go:embed live_validating_webhook.yaml
LiveValidatingWebhookYaml string
//go:embed desired_validating_webhook.yaml
DesiredValidatingWebhookYaml string
)

View File

@@ -0,0 +1,32 @@
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
labels:
app.kubernetes.io/instance: external-secrets
external-secrets.io/component: webhook
name: externalsecret-validate
webhooks:
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
caBundle: Cg==
service:
name: external-secrets-webhook
namespace: external-secrets
path: /validate-external-secrets-io-v1beta1-externalsecret
name: validate.externalsecret.external-secrets.io
rules:
- apiGroups:
- external-secrets.io
apiVersions:
- v1beta1
operations:
- CREATE
- UPDATE
- DELETE
resources:
- externalsecrets
scope: Namespaced
sideEffects: None
timeoutSeconds: 5

View File

@@ -0,0 +1,91 @@
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: >
{"apiVersion":"admissionregistration.k8s.io/v1","kind":"ValidatingWebhookConfiguration","metadata":{"annotations":{},"labels":{"app.kubernetes.io/instance":"external-secrets","external-secrets.io/component":"webhook"},"name":"externalsecret-validate"},"webhooks":[{"admissionReviewVersions":["v1","v1beta1"],"clientConfig":{"caBundle":"Cg==","service":{"name":"external-secrets-webhook","namespace":"external-secrets","path":"/validate-external-secrets-io-v1beta1-externalsecret"}},"name":"validate.externalsecret.external-secrets.io","rules":[{"apiGroups":["external-secrets.io"],"apiVersions":["v1beta1"],"operations":["CREATE","UPDATE","DELETE"],"resources":["externalsecrets"],"scope":"Namespaced"}],"sideEffects":"None","timeoutSeconds":5}]}
creationTimestamp: '2022-04-12T14:17:35Z'
generation: 2
labels:
app.kubernetes.io/instance: external-secrets
external-secrets.io/component: webhook
managedFields:
- apiVersion: admissionregistration.k8s.io/v1
fieldsType: FieldsV1
fieldsV1:
'f:metadata':
'f:annotations':
.: {}
'f:kubectl.kubernetes.io/last-applied-configuration': {}
'f:labels':
.: {}
'f:app.kubernetes.io/instance': {}
'f:external-secrets.io/component': {}
'f:webhooks':
.: {}
'k:{"name":"validate.externalsecret.external-secrets.io"}':
.: {}
'f:admissionReviewVersions': {}
'f:clientConfig':
.: {}
'f:service':
.: {}
'f:name': {}
'f:namespace': {}
'f:path': {}
'f:port': {}
'f:failurePolicy': {}
'f:matchPolicy': {}
'f:name': {}
'f:namespaceSelector': {}
'f:objectSelector': {}
'f:rules': {}
'f:sideEffects': {}
'f:timeoutSeconds': {}
manager: argocd
operation: Update
time: '2022-04-12T14:17:35Z'
- apiVersion: admissionregistration.k8s.io/v1
fieldsType: FieldsV1
fieldsV1:
'f:webhooks':
'k:{"name":"validate.externalsecret.external-secrets.io"}':
'f:clientConfig':
'f:caBundle': {}
manager: external-secrets
operation: Update
time: '2022-04-12T14:17:37Z'
name: externalsecret-validate
resourceVersion: '1644596'
uid: b56ccc4e-30d6-4b32-8a6e-7eae41ab3155
webhooks:
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
caBundle: >-
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURSakNDQWk2Z0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREEyTVJrd0Z3WURWUVFLRXhCbGVIUmwKY201aGJDMXpaV055WlhSek1Sa3dGd1lEVlFRREV4QmxlSFJsY201aGJDMXpaV055WlhSek1CNFhEVEl5TURReApNakV6TVRjek4xb1hEVE15TURRd09URTBNVGN6TjFvd05qRVpNQmNHQTFVRUNoTVFaWGgwWlhKdVlXd3RjMlZqCmNtVjBjekVaTUJjR0ExVUVBeE1RWlhoMFpYSnVZV3d0YzJWamNtVjBjekNDQVNJd0RRWUpLb1pJaHZjTkFRRUIKQlFBRGdnRVBBRENDQVFvQ2dnRUJBTU9RQmR2Z210RE1aVjRhNGQ2dUw5ZGNzT3c4SXRnbW9zZ3R1MGplTlF2Ygo4a291TmdRMVpxMlFSVFVNTTVCYlpNRTNGWHM3aWxwNVZVbzN3SnZsaVdVVHhxb3lIMUY2VUszbUsyYmp2aHRrCnVEYWVnNkh4ZzNjRlVybXRvNCtyVHNTT1BlN3ZRajVNbWZzeVEzb1BXamxFbExyMEE5b3RScGZnZGZtNWxncHgKVkE0SFdGeWZmQ3hpUEFaamNYNFdjd1hOdzJSN21aQnNNSW1xTk1YOUhzUEVOdTdzdk1DeXEzU0pvdzNqTXFpNgpHUFZaUmh2ZlRSY2hDcmV2UVE3OTRPNGkrSVk3ZVdvV00yZDgweVM3V09LcUUvNEE1SU9tNWVJK1BhNUlvd3E1CnppckxxU3lsYW15bzZxbWN3TDFEbFpiM2RmSE9GVUx0cFM1YkhTSzQyTWNDQXdFQUFhTmZNRjB3RGdZRFZSMFAKQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3SFFZRFZSME9CQllFRk1QMkF1aUh1d2FsczlTcgpYWk1XODdyb2l0UElNQnNHQTFVZEVRUVVNQktDRUdWNGRHVnlibUZzTFhObFkzSmxkSE13RFFZSktvWklodmNOCkFRRUxCUUFEZ2dFQkFMek5BczhnS2FqYjc1N3pyMjdHRzBMVzkxVG1ab1dPQ0ZHMXFrUWJ3T2U0d25kV2NiT08KbThsYkx6a291Wlo5d1I0aXN2OVFHYnNlS0V1UXpyWlZzZXlJTHZoUGVWcGZGd1ZkcVFsQ0laRXM5SSswd0hXawplblFWWGNEamZMTk9zdDhFcDlKVktwSkJwODRIY1NvZkJMY1RPcFdqdGZtZnNudmlzbU5ha2hGNzM2SmJrQUdmClZvdUJDQlU5Z3g2SGI5T2FDaDdpekZLMnVyWHo1NkV5eXhhUUlsckRyYVlZV3Mrb3ZhTlJwdEltKytqcnFBdUkKV0xxdWQvU0tQMy9Fc3o3cmVWb2xGODFIYmdEMEQ0RWlmZWJZeXpnWEJMcVlZcUxUZXIzQzVONFRwcGpJSi82NgpERVBNZ0xUaG9jRkpZNVFBYy9rbGl5Q2VnN3VoWSs5TnFLRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
service:
name: external-secrets-webhook
namespace: external-secrets
path: /validate-external-secrets-io-v1beta1-externalsecret
port: 443
failurePolicy: Fail
matchPolicy: Equivalent
name: validate.externalsecret.external-secrets.io
namespaceSelector: {}
objectSelector: {}
rules:
- apiGroups:
- external-secrets.io
apiVersions:
- v1beta1
operations:
- CREATE
- UPDATE
- DELETE
resources:
- externalsecrets
scope: Namespaced
sideEffects: None
timeoutSeconds: 5

View File

@@ -2,16 +2,19 @@ package clusterauth
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/argoproj/argo-cd/v2/common"
jwt "github.com/golang-jwt/jwt/v4"
log "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
)
@@ -173,7 +176,7 @@ func upsertRoleBinding(clientset kubernetes.Interface, name string, roleName str
}
// InstallClusterManagerRBAC installs RBAC resources for a cluster manager to operate a cluster. Returns a token
func InstallClusterManagerRBAC(clientset kubernetes.Interface, ns string, namespaces []string) (string, error) {
func InstallClusterManagerRBAC(clientset kubernetes.Interface, ns string, namespaces []string, bearerTokenTimeout time.Duration) (string, error) {
err := CreateServiceAccount(clientset, ArgoCDManagerServiceAccount, ns)
if err != nil {
@@ -212,42 +215,123 @@ func InstallClusterManagerRBAC(clientset kubernetes.Interface, ns string, namesp
}
}
return GetServiceAccountBearerToken(clientset, ns, ArgoCDManagerServiceAccount)
return GetServiceAccountBearerToken(clientset, ns, ArgoCDManagerServiceAccount, bearerTokenTimeout)
}
// GetServiceAccountBearerToken will attempt to get the provided service account until it
// exists, iterate the secrets associated with it looking for one of type
// kubernetes.io/service-account-token, and return it's token if found.
func GetServiceAccountBearerToken(clientset kubernetes.Interface, ns string, sa string) (string, error) {
var serviceAccount *corev1.ServiceAccount
// GetServiceAccountBearerToken determines if a ServiceAccount has a
// bearer token secret to use or if a secret should be created. It then
// waits for the secret to have a bearer token if a secret needs to
// be created and returns the token in encoded base64.
func GetServiceAccountBearerToken(clientset kubernetes.Interface, ns string, sa string, timeout time.Duration) (string, error) {
secretName, err := getOrCreateServiceAccountTokenSecret(clientset, sa, ns)
if err != nil {
return "", err
}
var secret *corev1.Secret
var err error
err = wait.Poll(500*time.Millisecond, 30*time.Second, func() (bool, error) {
serviceAccount, err = clientset.CoreV1().ServiceAccounts(ns).Get(context.Background(), sa, metav1.GetOptions{})
err = wait.PollImmediate(500*time.Millisecond, timeout, func() (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), common.ClusterAuthRequestTimeout)
defer cancel()
secret, err = clientset.CoreV1().Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})
if err != nil {
return false, err
return false, fmt.Errorf("failed to get secret %q for serviceaccount %q: %w", secretName, sa, err)
}
// Scan all secrets looking for one of the correct type:
for _, oRef := range serviceAccount.Secrets {
var getErr error
secret, err = clientset.CoreV1().Secrets(ns).Get(context.Background(), oRef.Name, metav1.GetOptions{})
if err != nil {
return false, fmt.Errorf("Failed to retrieve secret %q: %v", oRef.Name, getErr)
}
if secret.Type == corev1.SecretTypeServiceAccountToken {
return true, nil
}
_, ok := secret.Data["token"]
if !ok {
return false, nil
}
return false, nil
return true, nil
})
if err != nil {
return "", fmt.Errorf("Failed to wait for service account secret: %v", err)
return "", fmt.Errorf("failed to get token for serviceaccount %q: %w", sa, err)
}
token, ok := secret.Data["token"]
if !ok {
return "", fmt.Errorf("Secret %q for service account %q did not have a token", secret.Name, serviceAccount)
return string(secret.Data["token"]), nil
}
// getOrCreateServiceAccountTokenSecret will check if a ServiceAccount
// already has a kubernetes.io/service-account-token secret associated
// with it or creates one if the ServiceAccount doesn't have one. This
// was added to help add k8s v1.24+ clusters.
func getOrCreateServiceAccountTokenSecret(clientset kubernetes.Interface, sa, ns string) (string, error) {
// Wait for sa to have secret, but don't wait too
// long for 1.24+ clusters
var serviceAccount *corev1.ServiceAccount
err := wait.PollImmediate(500*time.Millisecond, 5*time.Second, func() (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), common.ClusterAuthRequestTimeout)
defer cancel()
var getErr error
serviceAccount, getErr = clientset.CoreV1().ServiceAccounts(ns).Get(ctx, sa, metav1.GetOptions{})
if getErr != nil {
return false, fmt.Errorf("failed to get serviceaccount %q: %w", sa, getErr)
}
if len(serviceAccount.Secrets) == 0 {
return false, nil
}
return true, nil
})
if err != nil && err != wait.ErrWaitTimeout {
return "", fmt.Errorf("failed to get serviceaccount token secret: %w", err)
}
return string(token), nil
if serviceAccount == nil {
log.Errorf("Unexpected nil serviceaccount '%s/%s' with no error returned", ns, sa)
return "", fmt.Errorf("failed to create serviceaccount token secret: nil serviceaccount returned for '%s/%s' with no error", ns, sa)
}
outerCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
for _, s := range serviceAccount.Secrets {
innerCtx, cancel := context.WithTimeout(outerCtx, common.ClusterAuthRequestTimeout)
defer cancel()
existingSecret, err := clientset.CoreV1().Secrets(ns).Get(innerCtx, s.Name, metav1.GetOptions{})
if err != nil {
return "", fmt.Errorf("failed to retrieve secret %q: %w", s.Name, err)
}
if existingSecret.Type == corev1.SecretTypeServiceAccountToken {
return existingSecret.Name, nil
}
}
return createServiceAccountToken(clientset, serviceAccount)
}
func createServiceAccountToken(clientset kubernetes.Interface, serviceAccount *corev1.ServiceAccount) (string, error) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
GenerateName: serviceAccount.Name + "-token-",
Namespace: serviceAccount.Namespace,
Annotations: map[string]string{
corev1.ServiceAccountNameKey: serviceAccount.Name,
},
},
Type: corev1.SecretTypeServiceAccountToken,
}
ctx, cancel := context.WithTimeout(context.Background(), common.ClusterAuthRequestTimeout)
defer cancel()
secret, err := clientset.CoreV1().Secrets(serviceAccount.Namespace).Create(ctx, secret, metav1.CreateOptions{})
if err != nil {
return "", fmt.Errorf("failed to create secret for serviceaccount %q: %w", serviceAccount.Name, err)
}
log.Infof("Created bearer token secret for ServiceAccount %q", serviceAccount.Name)
serviceAccount.Secrets = []corev1.ObjectReference{{
Name: secret.Name,
Namespace: secret.Namespace,
}}
patch, err := json.Marshal(serviceAccount)
if err != nil {
return "", fmt.Errorf("failed marshaling patch for serviceaccount %q: %w", serviceAccount.Name, err)
}
_, err = clientset.CoreV1().ServiceAccounts(serviceAccount.Namespace).Patch(ctx, serviceAccount.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{})
if err != nil {
return "", fmt.Errorf("failed to patch serviceaccount %q with bearer token secret: %w", serviceAccount.Name, err)
}
return secret.Name, nil
}
// UninstallClusterManagerRBAC removes RBAC resources for a cluster manager to operate a cluster

Some files were not shown because too many files have changed in this diff Show More