Compare commits

...

71 Commits

Author SHA1 Message Date
argo-bot
6dbbb18aa9 Bump version to 1.8.6 2021-02-26 21:12:06 +00:00
argo-bot
62b9b3aeb5 Bump version to 1.8.6 2021-02-26 21:11:50 +00:00
jannfis
31110cde4d fix: Properly escape HTML for error message from CLI SSO (#5563)
Signed-off-by: jannfis <jann@mistrust.net>
2021-02-26 10:29:50 +01:00
Alexander Matyushentsev
d6c5c72eb4 fix: API server should not print resource body when resource update fails (#5617)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-02-25 19:25:22 -08:00
kshamajain99
0b3333ef4b fix: fix memory leak in application controller (#5604)
fix: fix memory leak in application controller
2021-02-25 19:25:18 -08:00
argo-bot
d0f8edfec8 Bump version to 1.8.5 2021-02-20 05:29:23 +00:00
argo-bot
b1ff29fdf9 Bump version to 1.8.5 2021-02-20 05:29:05 +00:00
Alexander Matyushentsev
785bb9ecce fix: 'argocd app wait --suspended' stuck if operation is in progress (#5511)
* fix: 'argocd app wait --suspended' stuck if operation is in progress

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-02-19 14:41:58 -08:00
Alexander Matyushentsev
b57579c4ae fix: Presync hooks stop working after namespace resource is added in a Helm chart #5522 2021-02-19 09:50:56 -08:00
Ajay Kemparaj
6b53ac785e docs: add the missing rbac resources to the documentation (#5476)
* Adds resources accounts and gpgkeys

Signed-off-by: ajayk <ajaykemparaj@gmail.com>
2021-02-13 09:05:07 +01:00
Alexander Matyushentsev
e38920f570 refactor: optimize argocd-application-controller redis usage (#5345)
* refactor: controller uses two level caching to reduce number of redis calls

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-02-05 10:24:03 -08:00
argo-bot
28aea3dfde Bump version to 1.8.4 2021-02-05 17:46:03 +00:00
argo-bot
fe59190a96 Bump version to 1.8.4 2021-02-05 17:45:46 +00:00
Alexander Matyushentsev
0a04a491d9 fix: version info should be avaialble if anonymous access is enabled (#5422)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-02-05 09:17:43 -08:00
kshamajain99
701ce05393 fix: disable jwt claim audience validation #5381 (#5413)
* fix: disable audience validation

Signed-off-by: kshamajain99 <kshamajain99@gmail.com>

* update other places

Signed-off-by: kshamajain99 <kshamajain99@gmail.com>
2021-02-05 09:17:39 -08:00
Alexander Matyushentsev
965825f752 fix: /api/version should not return tools version for unauthenticated requests (#5415)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-02-05 09:17:31 -08:00
Alexander Matyushentsev
bd73326b8a fix: account tokens should be rejected if required capability is disabled (#5414)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-02-05 09:16:56 -08:00
Alexander Matyushentsev
502b8944c4 feat: set X-XSS-Protection while serving static content (#5412)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-02-05 09:16:52 -08:00
Alexander Matyushentsev
f5b0db240b fix: tokens keep working after account is deactivated (#5402)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-02-05 09:16:48 -08:00
Alexander Matyushentsev
ce43b7a438 fix: a request which was using a revoked project token, would still be allowed to perform requests allowed by default policy (#5378)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-02-05 09:16:44 -08:00
Liviu Costea
ebcfea64ff refactor(jwt): use typed access to claims (#5075)
Signed-off-by: Liviu Costea <email.lcostea@gmail.com>
2021-02-05 09:16:31 -08:00
Regina Scott
14c3dd2c59 fix: overriding version logic in warning banner (#5410)
Signed-off-by: Regina Scott <rescott@redhat.com>
2021-02-04 12:53:36 -08:00
Regina Scott
4359d345a0 feat: add versioning to argocd docs (#5099)
* feat: add versioning to argocd docs

Signed-off-by: Regina Scott <rescott@redhat.com>

* make default branch stable, provide warning for latest

Signed-off-by: Regina Scott <rescott@redhat.com>
2021-02-04 12:24:34 -08:00
Regina Scott
7081068a2d fix: Capitalization in toc (#5024)
Signed-off-by: Regina Scott <rescott@redhat.com>
2021-02-04 12:24:24 -08:00
argo-bot
0f9c684278 Bump version to 1.8.3 2021-01-21 22:09:58 +00:00
argo-bot
3ea3c13665 Bump version to 1.8.3 2021-01-21 22:09:44 +00:00
Alexander Matyushentsev
13fed83ec6 fix: make sure JWT token time fields contain only integer values (#5228)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-01-11 13:59:58 -08:00
argo-bot
94017f2c8d Bump version to 1.8.2 2021-01-10 05:30:48 +00:00
argo-bot
3c53ea6cff Bump version to 1.8.2 2021-01-10 05:30:32 +00:00
kshamajain99
7b2946962d updating cluster drops secret (#5220)
Signed-off-by: kshamajain99 <kshamajain99@gmail.com>
2021-01-09 12:21:12 -08:00
jannfis
df3422798d chore: Upgrade gorilla/handlers and gorilla/websocket (#5186)
* chore: Upgrade gorilla/handlers and gorilla/websocket

Signed-off-by: jannfis <jann@mistrust.net>

* go mod tidy

Signed-off-by: jannfis <jann@mistrust.net>
2021-01-06 12:23:33 +01:00
jannfis
f44855fa4d chore: Upgrade jwt-go to 4.0.0-preview1 (#5184)
Signed-off-by: jannfis <jann@mistrust.net>
2021-01-06 09:44:32 +01:00
Alexander Matyushentsev
1f4a052da3 fix: remove invalid assumption about OCI helm chart path (#5179)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-01-06 00:08:40 -08:00
jannfis
74f5eae750 fix: Possible nil pointer dereference in repository API (#5128)
Signed-off-by: jannfis <jann@mistrust.net>
2021-01-05 19:26:28 +01:00
jannfis
36a9465d85 fix: Possible nil pointer dereference in repocreds API (#5130)
Signed-off-by: jannfis <jann@mistrust.net>
2021-01-05 19:26:06 +01:00
Alexander Matyushentsev
9e6c04700e fix: use json serialization to store cache instead of github.com/vmihailenco/msgpack (#4965)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-01-04 22:42:04 -08:00
Alexander Matyushentsev
8abe96ad9a fix: add liveness probe to restart repo server if it fails to server tls requests (#5110) (#5119)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2021-01-04 22:36:29 -08:00
jannfis
10de6e7cfc fix: Allow correct SSO redirect URL for CLI static client (#5098)
Signed-off-by: jannfis <jann@mistrust.net>
2020-12-22 08:31:42 +01:00
May Zhang
e57071a150 fix: add grpc health check (#5060)
* fix: add grpc health check

Signed-off-by: May Zhang <may_zhang@intuit.com>

* fix: fixing lint error

Signed-off-by: May Zhang <may_zhang@intuit.com>

* fix: fixing lint error

Signed-off-by: May Zhang <may_zhang@intuit.com>
2020-12-15 13:21:24 -08:00
jannfis
41db5fc010 chore: Update Dex to v2.27.0 (#5058)
Signed-off-by: jannfis <jann@mistrust.net>
2020-12-15 18:23:16 +01:00
Alexander Matyushentsev
31f257e957 fix: setting 'revision history limit' errors in UI (#5035)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2020-12-11 10:35:09 -08:00
Alexander Matyushentsev
5fd93a7db5 fix: add api-server liveness probe that catches bad data in informer (#5026)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2020-12-11 10:35:02 -08:00
argo-bot
c2547dca95 Bump version to 1.8.1 2020-12-10 02:48:59 +00:00
argo-bot
522ed90f38 Bump version to 1.8.1 2020-12-10 02:48:47 +00:00
Alexander Matyushentsev
7a0266f0fb fix: sync retry is broken for multi-phase syncs (#5017)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2020-12-09 18:11:44 -08:00
argo-bot
30348417a9 Bump version to 1.8.0 2020-12-09 18:19:22 +00:00
argo-bot
910eddbbf3 Bump version to 1.8.0 2020-12-09 18:19:09 +00:00
Alexander Matyushentsev
f150ba18fb fix: infer app destination server in indexer to prevent concurrent app object modification (#4993)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2020-12-08 08:57:50 -08:00
jannfis
97d030b4b2 chore: Fix erroneous path expansion in release action (#4907)
Signed-off-by: jannfis <jann@mistrust.net>
2020-12-03 13:21:12 -08:00
Michael Goodness
5df269b3fa fix(repository.go): rename .argocd-source.yaml in error message (#4964)
Signed-off-by: Michael Goodness <michael.goodness@mlb.com>
2020-12-03 09:49:10 -08:00
argo-bot
775f9711e7 Bump version to 1.8.0-rc2 2020-12-03 04:59:41 +00:00
argo-bot
3f974825a6 Bump version to 1.8.0-rc2 2020-12-03 04:59:28 +00:00
Alexander Matyushentsev
1d55439f7f increase cache version (#4957)
Signed-off-by: Alexander Matyushentsev <Alexander_Matyushentsev@intuit.com>
2020-12-02 17:06:08 -08:00
Maxime Brunet
41daf71851 chore: Upgrade go-jsonnet to v0.17.0 (#4891)
* chore: Upgrade go-jsonnet to v0.17.0

Signed-off-by: Maxime Brunet <max@brnt.mx>

* Fix vm.EvaluateSnippet is deprecated

Use EvaluateFile or EvaluateAnonymousSnippet instead.

Signed-off-by: Maxime Brunet <max@brnt.mx>

* Do not read Jsonnet files

Signed-off-by: Maxime Brunet <max@brnt.mx>
2020-12-02 17:06:03 -08:00
Jesse Suen
f4796398a8 fix: rollout health could incorrectly report v0.9 rollouts as Progressing (#4949)
Signed-off-by: Jesse Suen <Jesse_Suen@intuit.com>
2020-12-02 13:45:04 -08:00
Alexander Matyushentsev
0f0b6ce278 fix: reset cached manifest generation errors after 1hr instead of 12 requests (#4953)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2020-12-02 13:37:45 -08:00
Alexander Matyushentsev
aee4dfaa1e fix: cache missing app path and commit verification errors (#4947)
* fix: cache missing app path and commit verification errors

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2020-12-02 12:21:29 -08:00
Alexander Matyushentsev
74a92c6031 fix: upgrades github.com/vmihailenco/msgpack/v5 to fix #4933 (#4952)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2020-12-02 12:21:25 -08:00
Alexander Matyushentsev
4237c6f00f fix: correctly compare application destinations with inferred cluster URL (#4937)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2020-12-01 11:40:33 -08:00
Alexander Matyushentsev
0f3d74fa58 refactor: upgrade helm to v3.4.1 (#4938)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2020-12-01 11:40:27 -08:00
argo-bot
868516f0bd Bump version to 1.8.0-rc1 2020-11-25 18:03:58 +00:00
argo-bot
e50c43fb3f Bump version to 1.8.0-rc1 2020-11-25 18:03:44 +00:00
Alexander Matyushentsev
9b6a0dc3cd refactor: disable gRPC metrics by default (#4892)
Signed-off-by: Alexander Matyushentsev <Alexander_Matyushentsev@intuit.com>
2020-11-23 16:45:34 -08:00
Alexander Matyushentsev
1f63e99b78 docs: add v1.8 changelog and upgrading instructions (#4888)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2020-11-23 16:45:31 -08:00
Alexander Matyushentsev
589ad5d2ac fix: upgrade gitops-engine version. (fixes #4877) (#4890)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2020-11-23 13:02:59 -08:00
kshamajain99
4464f99df7 fix: validate empty server address for destination cluster (#4852) (#4860)
* Always set inferred destination server

Signed-off-by: kshamajain99 <kshamajain99@gmail.com>
2020-11-20 16:46:24 -08:00
Jaideep Raghunath Rao
65a2d9f1ff feat: Allow configuration of OIDC logout URL to invalidate SSO session after logout (#4452) (#4826)
feat: Allow configuration of OIDC logout URL to invalidate SSO session after logout (#4452) (#4826)

Signed-off-by: jaideepr97 <jaideep.r97@gmail.com>
2020-11-20 11:21:11 -08:00
Sho Okada
bcaabc51a1 fix: argocd app patch remove does not work (#4585)
Signed-off-by: Sho Okada <shokada3@gmail.com>
2020-11-20 11:21:07 -08:00
Alexander Matyushentsev
9140fea0fb fix: increase max grpc message size (#4869)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2020-11-20 11:20:59 -08:00
Alexander Matyushentsev
d83e0ddb56 chore: use release tag to reference gitops engine dependency (#4866)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2020-11-19 14:10:31 -08:00
Mikhail Nacharov
25ca589bff fix: Adds podAntiAffinity in base manifests (#4549) (#4599)
Signed-off-by: Mikhail Vladimirovich Nacharov <author@webnach.ru>
2020-11-19 12:08:56 -08:00
103 changed files with 2426 additions and 601 deletions

View File

@@ -44,7 +44,7 @@ jobs:
# Target version must match major.minor.patch and optional -rcX suffix
# where X must be a number.
TARGET_VERSION=${SOURCE_TAG#*release-v}
if ! echo ${TARGET_VERSION} | egrep '^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)*$'; then
if ! echo "${TARGET_VERSION}" | egrep '^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)*$'; then
echo "::error::Target version '${TARGET_VERSION}' is malformed, refusing to continue." >&2
exit 1
fi
@@ -101,16 +101,16 @@ jobs:
# Whatever is in commit history for the tag, we only want that
# annotation from our tag. We discard everything else.
if test "$begin" = "false"; then
if echo $line | grep -q "tag ${SOURCE_TAG#refs/tags/}"; then begin="true"; fi
if echo "$line" | grep -q "tag ${SOURCE_TAG#refs/tags/}"; then begin="true"; fi
continue
fi
if test "$prefix" = "true"; then
if test -z "$line"; then prefix=false; fi
else
if echo $line | egrep -q '^commit [0-9a-f]+'; then
if echo "$line" | egrep -q '^commit [0-9a-f]+'; then
break
fi
echo $line >> ${RELEASE_NOTES}
echo "$line" >> ${RELEASE_NOTES}
fi
done

7
.readthedocs.yml Normal file
View File

@@ -0,0 +1,7 @@
version: 2
formats: all
mkdocs:
fail_on_warning: false
python:
install:
- requirements: docs/requirements.txt

View File

@@ -1,5 +1,63 @@
# Changelog
## v1.8.0 (Unreleased)
### Mono-Repository Improvements
Enhanced performance during manifest generation from mono-repository - the repository that represents the
desired state of the whole cluster and contains hundreds of applications. The improved argocd-repo-server
now able to concurrently generate manifests from the same repository and for the same commit SHA. This
might provide 10x performance improvement of manifests generation.
### Annotation Based Path Detection
The feature that allows specifying which source repository directories influence the application manifest generation
using the `argocd.argoproj.io/manifest-generate-paths` annotation. The annotation improves the Git webhook handler
behavior. The webhook avoids related applications reconciliation if no related files have been changed by the Git commit
and even allows to skip manifests generation for new commit by re-using generation manifests for the previous commit.
### Horizontal Controller Scaling
This release allows scaling the `argocd-application-controller` horizontally. This allows you to manage as many Kubernetes clusters
as needed using a single Argo CD instance.
## New Core Functionality Features
Besides performance improvements, Argo CD got a lot of usability enhancements and new features:
* Namespace and CRD creation [#4354](https://github.com/argoproj/argo-cd/issues/4354)
* Unknown fields of built-in K8S types [#1787](https://github.com/argoproj/argo-cd/issues/1787)
* Endpoints Diffing [#1816](https://github.com/argoproj/argo-cd/issues/1816)
* Better compatibility with Helm Hooks [#1816](https://github.com/argoproj/argo-cd/issues/1816)
* App-of-Apps Health Assessment [#3781](https://github.com/argoproj/argo-cd/issues/3781)
## Global Projects
This release makes it easy to manage an Argo CD that has hundreds of Projects. Instead of duplicating the same organization-wide rules in all projects
you can put such rules into one project and make this project “global” for all other projects. Rules defined in the global project are inherited by all
other projects and therefore dont have to be duplicated. The sample below demonstrates how you can create a global project and specify which project should
inherit global project rules using Kubernetes labels.
## User Interface Improvements
The Argo CD user interface is an important part of a project and we keep working hard on improving the user experience. Here is an incomplete list of implemented improvements:
* Improved Applications Filters [#4622](https://github.com/argoproj/argo-cd/issues/4622)
* Git tags and branches autocompletion [#4713](https://github.com/argoproj/argo-cd/issues/4713)
* Project Details Page [#4400](https://github.com/argoproj/argo-cd/issues/4400)
* New version information panel [#4376](https://github.com/argoproj/argo-cd/issues/4376)
* Progress Indicators [#4411](https://github.com/argoproj/argo-cd/issues/4411)
* External links annotations [#4380](https://github.com/argoproj/argo-cd/issues/4380) and more!
## Config Management Tools Enhancements
* OCI Based Repositories [#4018](https://github.com/argoproj/argo-cd/issues/4018)
* Configurable Helm Versions [#4111](https://github.com/argoproj/argo-cd/issues/4111)
## Bug fixes and under the hood changes
In addition to new features and enhancements, weve fixed more than 50 bugs and upgraded third-party components and libraries that Argo CD relies on.
## v1.7.9 (2020-11-17)
- fix: improve commit verification tolerance (#4825)

View File

@@ -1,6 +1,6 @@
controller: 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} go run ./cmd/argocd-application-controller/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
api-server: 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} go run ./cmd/argocd-server/main.go --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} --staticassets ui/dist/app"
dex: sh -c "go run github.com/argoproj/argo-cd/cmd/argocd-util 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 quay.io/dexidp/dex:v2.25.0 serve /dex.yaml"
dex: sh -c "go run github.com/argoproj/argo-cd/cmd/argocd-util 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.27.0 serve /dex.yaml"
redis: docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} redis:5.0.10-alpine --save "" --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}
repo-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} 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} go run ./cmd/argocd-repo-server/main.go --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"
ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start'

View File

@@ -1 +1 @@
1.8.0
1.8.6

View File

@@ -78,6 +78,7 @@ func NewCommand() *cobra.Command {
cache, err := cacheSrc()
errors.CheckError(err)
cache.Cache.SetClient(cacheutil.NewTwoLevelClient(cache.Cache.GetClient(), 10*time.Minute))
settingsMgr := settings.NewSettingsManager(ctx, kubeClient, namespace)
kubectl := kubeutil.NewKubectl()

View File

@@ -12,9 +12,11 @@ import (
"github.com/go-redis/redis/v8"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"google.golang.org/grpc/health/grpc_health_v1"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/reposerver/apiclient"
reposervercache "github.com/argoproj/argo-cd/reposerver/cache"
"github.com/argoproj/argo-cd/reposerver/metrics"
"github.com/argoproj/argo-cd/reposerver/repository"
@@ -23,6 +25,8 @@ import (
"github.com/argoproj/argo-cd/util/env"
"github.com/argoproj/argo-cd/util/errors"
"github.com/argoproj/argo-cd/util/gpg"
"github.com/argoproj/argo-cd/util/healthz"
ioutil "github.com/argoproj/argo-cd/util/io"
"github.com/argoproj/argo-cd/util/tls"
)
@@ -32,8 +36,8 @@ const (
gnuPGSourcePath = "/app/config/gpg/source"
defaultPauseGenerationAfterFailedGenerationAttempts = 3
defaultPauseGenerationOnFailureForMinutes = 0
defaultPauseGenerationOnFailureForRequests = 12
defaultPauseGenerationOnFailureForMinutes = 60
defaultPauseGenerationOnFailureForRequests = 0
)
func getGnuPGSourcePath() string {
@@ -96,6 +100,28 @@ func NewCommand() *cobra.Command {
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", listenPort))
errors.CheckError(err)
healthz.ServeHealthCheck(http.DefaultServeMux, func(r *http.Request) error {
if val, ok := r.URL.Query()["full"]; ok && len(val) > 0 && val[0] == "true" {
// connect to itself to make sure repo server is able to serve connection
// used by liveness probe to auto restart repo server
// see https://github.com/argoproj/argo-cd/issues/5110 for more information
conn, err := apiclient.NewConnection(fmt.Sprintf("localhost:%d", listenPort), 60)
if err != nil {
return err
}
defer ioutil.Close(conn)
client := grpc_health_v1.NewHealthClient(conn)
res, err := client.Check(r.Context(), &grpc_health_v1.HealthCheckRequest{})
if err != nil {
return err
}
if res.Status != grpc_health_v1.HealthCheckResponse_SERVING {
return fmt.Errorf("grpc health check status is '%v'", res.Status)
}
return nil
}
return nil
})
http.Handle("/metrics", metricsServer.GetHandler())
go func() { errors.CheckError(http.ListenAndServe(fmt.Sprintf(":%d", metricsPort), nil)) }()

View File

@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"sort"
"time"
@@ -278,7 +279,7 @@ func reconcileApplications(
projLister := appInformerFactory.Argoproj().V1alpha1().AppProjects().Lister()
server, err := metrics.NewMetricsServer("", appLister, func(obj interface{}) bool {
return true
}, func() error {
}, func(r *http.Request) error {
return nil
})

View File

@@ -1901,7 +1901,7 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout
selectedResourcesAreReady = checkResourceStatus(watchSync, watchHealth, watchOperation, watchSuspended, string(app.Status.Health.Status), string(app.Status.Sync.Status), appEvent.Application.Operation)
}
if selectedResourcesAreReady && !operationInProgress {
if selectedResourcesAreReady && (!operationInProgress || !watchOperation) {
app = printFinalStatus(app)
return app, nil
}

View File

@@ -5,6 +5,7 @@ import (
"crypto/sha256"
"encoding/base64"
"fmt"
"html"
"net/http"
"os"
"strconv"
@@ -12,7 +13,7 @@ import (
"time"
"github.com/coreos/go-oidc"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/v4"
log "github.com/sirupsen/logrus"
"github.com/skratchdot/open-golang/open"
"github.com/spf13/cobra"
@@ -25,6 +26,7 @@ import (
"github.com/argoproj/argo-cd/util/errors"
grpc_util "github.com/argoproj/argo-cd/util/grpc"
"github.com/argoproj/argo-cd/util/io"
jwtutil "github.com/argoproj/argo-cd/util/jwt"
"github.com/argoproj/argo-cd/util/localconfig"
oidcutil "github.com/argoproj/argo-cd/util/oidc"
"github.com/argoproj/argo-cd/util/rand"
@@ -113,7 +115,7 @@ func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comman
}
parser := &jwt.Parser{
SkipClaimsValidation: true,
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation(), jwt.WithoutAudienceValidation()),
}
claims := jwt.MapClaims{}
_, _, err := parser.ParseUnverified(tokenString, &claims)
@@ -161,13 +163,13 @@ func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comman
}
func userDisplayName(claims jwt.MapClaims) string {
if email, ok := claims["email"]; ok && email != nil {
return email.(string)
if email := jwtutil.StringField(claims, "email"); email != "" {
return email
}
if name, ok := claims["name"]; ok && name != nil {
return name.(string)
if name := jwtutil.StringField(claims, "name"); name != "" {
return name
}
return claims["sub"].(string)
return jwtutil.StringField(claims, "sub")
}
// oauth2Login opens a browser, runs a temporary HTTP server to delegate OAuth2 login flow and
@@ -190,7 +192,7 @@ func oauth2Login(ctx context.Context, port int, oidcSettings *settingspkg.OIDCCo
var refreshToken string
handleErr := func(w http.ResponseWriter, errMsg string) {
http.Error(w, errMsg, http.StatusBadRequest)
http.Error(w, html.EscapeString(errMsg), http.StatusBadRequest)
completionChan <- errMsg
}
@@ -205,7 +207,7 @@ func oauth2Login(ctx context.Context, port int, oidcSettings *settingspkg.OIDCCo
log.Debugf("Callback: %s", r.URL)
if formErr := r.FormValue("error"); formErr != "" {
handleErr(w, formErr+": "+r.FormValue("error_description"))
handleErr(w, fmt.Sprintf("%s: %s", formErr, r.FormValue("error_description")))
return
}

View File

@@ -0,0 +1,31 @@
package commands
import (
"testing"
"github.com/dgrijalva/jwt-go/v4"
"github.com/stretchr/testify/assert"
)
//
func Test_userDisplayName_email(t *testing.T) {
claims := jwt.MapClaims{"iss": "qux", "sub": "foo", "email": "firstname.lastname@example.com", "groups": []string{"baz"}}
actualName := userDisplayName(claims)
expectedName := "firstname.lastname@example.com"
assert.Equal(t, expectedName, actualName)
}
func Test_userDisplayName_name(t *testing.T) {
claims := jwt.MapClaims{"iss": "qux", "sub": "foo", "name": "Firstname Lastname", "groups": []string{"baz"}}
actualName := userDisplayName(claims)
expectedName := "Firstname Lastname"
assert.Equal(t, expectedName, actualName)
}
func Test_userDisplayName_sub(t *testing.T) {
claims := jwt.MapClaims{"iss": "qux", "sub": "foo", "groups": []string{"baz"}}
actualName := userDisplayName(claims)
expectedName := "foo"
assert.Equal(t, expectedName, actualName)
}

View File

@@ -9,7 +9,7 @@ import (
"time"
timeutil "github.com/argoproj/pkg/time"
jwtgo "github.com/dgrijalva/jwt-go"
jwtgo "github.com/dgrijalva/jwt-go/v4"
"github.com/spf13/cobra"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
@@ -17,6 +17,7 @@ import (
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/errors"
"github.com/argoproj/argo-cd/util/io"
"github.com/argoproj/argo-cd/util/jwt"
)
const (
@@ -247,13 +248,10 @@ func NewProjectRoleCreateTokenCommand(clientOpts *argocdclient.ClientOptions) *c
}
claims := token.Claims.(jwtgo.MapClaims)
issuedAt := int64(claims["iat"].(float64))
expiresAt := int64(0)
if expires, ok := claims["exp"]; ok {
expiresAt = int64(expires.(float64))
}
id := claims["jti"].(string)
subject := claims["sub"].(string)
issuedAt, _ := jwt.IssuedAt(claims)
expiresAt := int64(jwt.Float64Field(claims, "exp"))
id := jwt.StringField(claims, "jti")
subject := jwt.StringField(claims, "sub")
if !outputTokenOnly {
fmt.Printf("Create token succeeded for %s.\n", subject)

View File

@@ -85,6 +85,8 @@ const (
DexAPIEndpoint = "/api/dex"
// LoginEndpoint is Argo CD's shorthand login endpoint which redirects to dex's OAuth 2.0 provider's consent page
LoginEndpoint = "/auth/login"
// LogoutEndpoint is Argo CD's shorthand logout endpoint which invalidates OIDC session after logout
LogoutEndpoint = "/auth/logout"
// CallbackEndpoint is Argo CD's final callback endpoint we reach after OAuth 2.0 login flow has been completed
CallbackEndpoint = "/auth/callback"
// DexCallbackEndpoint is Argo CD's final callback endpoint when Dex is configured
@@ -180,6 +182,8 @@ const (
EnvControllerReplicas = "ARGOCD_CONTROLLER_REPLICAS"
// EnvControllerShard is the shard number that should be handled by controller
EnvControllerShard = "ARGOCD_CONTROLLER_SHARD"
// EnvEnableGRPCTimeHistogramEnv enables gRPC metrics collection
EnvEnableGRPCTimeHistogramEnv = "ARGOCD_ENABLE_GRPC_TIME_HISTOGRAM"
)
const (
@@ -189,7 +193,7 @@ const (
MinClientVersion = "1.4.0"
// CacheVersion is a objects version cached using util/cache/cache.go.
// Number should be bumped in case of backward incompatible change to make sure cache is invalidated after upgrade.
CacheVersion = "1.8.1"
CacheVersion = "1.8.3"
)
// GetGnuPGHomePath retrieves the path to use for GnuPG home directory, which is either taken from GNUPGHOME environment or a default value

View File

@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"math"
"net/http"
"reflect"
"runtime/debug"
"sort"
@@ -13,12 +14,11 @@ import (
"sync"
"time"
logutils "github.com/argoproj/argo-cd/util/log"
"github.com/argoproj/gitops-engine/pkg/diff"
"github.com/argoproj/gitops-engine/pkg/health"
synccommon "github.com/argoproj/gitops-engine/pkg/sync/common"
"github.com/argoproj/gitops-engine/pkg/utils/kube"
jsonpatch "github.com/evanphx/json-patch"
log "github.com/sirupsen/logrus"
"golang.org/x/sync/semaphore"
v1 "k8s.io/api/core/v1"
@@ -26,9 +26,11 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
apiruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
@@ -42,7 +44,6 @@ import (
"github.com/argoproj/argo-cd/pkg/apis/application"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
appinformers "github.com/argoproj/argo-cd/pkg/client/informers/externalversions"
"github.com/argoproj/argo-cd/pkg/client/informers/externalversions/application/v1alpha1"
applisters "github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
"github.com/argoproj/argo-cd/reposerver/apiclient"
@@ -51,6 +52,7 @@ import (
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/errors"
"github.com/argoproj/argo-cd/util/glob"
logutils "github.com/argoproj/argo-cd/util/log"
settings_util "github.com/argoproj/argo-cd/util/settings"
)
@@ -151,10 +153,7 @@ func NewApplicationController(
ctrl.kubectlSemaphore = semaphore.NewWeighted(kubectlParallelismLimit)
}
kubectl.SetOnKubectlRun(ctrl.onKubectlRun)
appInformer, appLister, err := ctrl.newApplicationInformerAndLister()
if err != nil {
return nil, err
}
appInformer, appLister := ctrl.newApplicationInformerAndLister()
indexers := cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}
projInformer := v1alpha1.NewAppProjectInformer(applicationClientset, namespace, appResyncPeriod, indexers)
projInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
@@ -175,7 +174,8 @@ func NewApplicationController(
},
})
metricsAddr := fmt.Sprintf("0.0.0.0:%d", metricsPort)
ctrl.metricsServer, err = metrics.NewMetricsServer(metricsAddr, appLister, ctrl.canProcessApp, func() error {
var err error
ctrl.metricsServer, err = metrics.NewMetricsServer(metricsAddr, appLister, ctrl.canProcessApp, func(r *http.Request) error {
return nil
})
if err != nil {
@@ -854,6 +854,11 @@ func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Appli
ctrl.requestAppRefresh(app.Name, CompareWithLatest.Pointer(), &retryAfter)
return
} else {
// retrying operation. remove previous failure time in app since it is used as a trigger
// that previous failed and operation should be retried
state.FinishedAt = nil
ctrl.setOperationState(app, state)
// Get rid of sync results and null out previous operation completion time
state.SyncResult = nil
}
} else {
@@ -944,6 +949,13 @@ func (ctrl *ApplicationController) setOperationState(app *appv1.Application, sta
if err != nil {
return err
}
if app.Status.OperationState != nil && app.Status.OperationState.FinishedAt != nil && state.FinishedAt == nil {
patchJSON, err = jsonpatch.MergeMergePatches(patchJSON, []byte(`{"status": {"operationState": {"finishedAt": null}}}`))
if err != nil {
return err
}
}
appClient := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace)
_, err = appClient.Patch(context.Background(), app.Name, types.MergePatchType, patchJSON, metav1.PatchOptions{})
if err != nil {
@@ -1392,11 +1404,6 @@ func (ctrl *ApplicationController) canProcessApp(obj interface{}) bool {
if !ok {
return false
}
err := argo.ValidateDestination(context.Background(), &app.Spec.Destination, ctrl.db)
if err != nil {
ctrl.setAppCondition(app, appv1.ApplicationCondition{Type: appv1.ApplicationConditionInvalidSpecError, Message: err.Error()})
return false
}
if ctrl.clusterFilter != nil {
cluster, err := ctrl.db.GetCluster(context.Background(), app.Spec.Destination.Server)
if err != nil {
@@ -1408,15 +1415,47 @@ func (ctrl *ApplicationController) canProcessApp(obj interface{}) bool {
return true
}
func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.SharedIndexInformer, applisters.ApplicationLister, error) {
appInformerFactory := appinformers.NewFilteredSharedInformerFactory(
ctrl.applicationClientset,
func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.SharedIndexInformer, applisters.ApplicationLister) {
informer := cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (apiruntime.Object, error) {
return ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace).List(context.TODO(), options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
return ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace).Watch(context.TODO(), options)
},
},
&appv1.Application{},
ctrl.statusRefreshTimeout,
ctrl.namespace,
func(options *metav1.ListOptions) {},
cache.Indexers{
cache.NamespaceIndex: func(obj interface{}) ([]string, error) {
app, ok := obj.(*appv1.Application)
if ok {
if err := argo.ValidateDestination(context.Background(), &app.Spec.Destination, ctrl.db); err != nil {
ctrl.setAppCondition(app, appv1.ApplicationCondition{Type: appv1.ApplicationConditionInvalidSpecError, Message: err.Error()})
}
}
return cache.MetaNamespaceIndexFunc(obj)
},
orphanedIndex: func(obj interface{}) (i []string, e error) {
app, ok := obj.(*appv1.Application)
if !ok {
return nil, nil
}
proj, err := ctrl.getAppProj(app)
if err != nil {
return nil, nil
}
if proj.Spec.OrphanedResources != nil {
return []string{app.Spec.Destination.Namespace}, nil
}
return nil, nil
},
},
)
informer := appInformerFactory.Argoproj().V1alpha1().Applications().Informer()
lister := appInformerFactory.Argoproj().V1alpha1().Applications().Lister()
lister := applisters.NewApplicationLister(informer.GetIndexer())
informer.AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
@@ -1461,24 +1500,7 @@ func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.Shar
},
},
)
err := informer.AddIndexers(cache.Indexers{
orphanedIndex: func(obj interface{}) (i []string, e error) {
app, ok := obj.(*appv1.Application)
if !ok {
return nil, nil
}
proj, err := ctrl.getAppProj(app)
if err != nil {
return nil, nil
}
if proj.Spec.OrphanedResources != nil {
return []string{app.Spec.Destination.Namespace}, nil
}
return nil, nil
},
})
return informer, lister, err
return informer, lister
}
func (ctrl *ApplicationController) RegisterClusterSecretUpdater(ctx context.Context) {

View File

@@ -1006,26 +1006,6 @@ func TestUpdateReconciledAt(t *testing.T) {
}
func TestCanProcessApp_DestNameIsValid(t *testing.T) {
app := newFakeAppWithDestName()
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app, &defaultProj}})
ok := ctrl.canProcessApp(app)
assert.True(t, ok)
assert.Len(t, app.Status.Conditions, 0)
}
func TestCanProcessApp_BothDestNameAndServer(t *testing.T) {
app := newFakeAppWithDestMismatch()
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app, &defaultProj}})
ok := ctrl.canProcessApp(app)
assert.False(t, ok)
assert.Len(t, app.Status.Conditions, 1)
assert.Equal(t, argoappv1.ApplicationConditionInvalidSpecError, app.Status.Conditions[0].Type)
assert.Equal(t, "application destination can't have both name and server defined: another-cluster https://localhost:6443", app.Status.Conditions[0].Message)
}
func TestFinalizeProjectDeletion_HasApplications(t *testing.T) {
app := newFakeApp()
proj := &argoappv1.AppProject{ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: test.FakeArgoCDNamespace}}

View File

@@ -294,7 +294,7 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
c.metricsServer.IncClusterEventsCount(cluster.Server, gvk.Group, gvk.Kind)
})
c.clusters[cluster.Server] = clusterCache
c.clusters[server] = clusterCache
return clusterCache, nil
}

View File

@@ -133,7 +133,7 @@ var (
)
// NewMetricsServer returns a new prometheus server which collects application metrics
func NewMetricsServer(addr string, appLister applister.ApplicationLister, appFilter func(obj interface{}) bool, healthCheck func() error) (*MetricsServer, error) {
func NewMetricsServer(addr string, appLister applister.ApplicationLister, appFilter func(obj interface{}) bool, healthCheck func(r *http.Request) error) (*MetricsServer, error) {
hostname, err := os.Hostname()
if err != nil {
return nil, err

View File

@@ -112,7 +112,7 @@ status:
status: Healthy
`
var noOpHealthCheck = func() error {
var noOpHealthCheck = func(r *http.Request) error {
return nil
}

172
docs/assets/versions.css Normal file
View File

@@ -0,0 +1,172 @@
.md-header-nav__title {
display: flex;
}
.dropdown-caret {
display: inline-block !important;
position: absolute;
right: 4px;
}
.fa .fa-caret-down {
display: none !important;
}
.rst-other-versions {
text-align: right;
}
.rst-other-versions > dl, .rst-other-versions dt, .rst-other-versions small {
display: none;
}
.rst-other-versions > dl:first-child {
display: flex !important;
flex-direction: column;
line-height: 0px !important;
}
.rst-versions.shift-up .rst-other-versions {
display: flex !important;
}
.rst-versions .rst-other-versions {
display: none;
}
/* Version Warning */
div[data-md-component=announce] {
background-color: rgba(255,145,0,.1);
}
div[data-md-component=announce]>div#announce-msg{
color: var(--md-admonition-fg-color);
font-size: .8rem;
text-align: center;
margin: 15px;
}
div[data-md-component=announce]>div#announce-msg>a{
color: var(--md-typeset-a-color);
text-decoration: underline;
}
/* from https://assets.readthedocs.org/static/css/badge_only.css,
most styles have to be overriden here */
.rst-versions{
position: relative !important;
bottom: 0;
left: 0;
width: 100px !important;
background: hsla(173, 100%, 24%, 1) !important;
font-family: inherit !important;
z-index: 0 !important;
}
.rst-versions a{
color:#2980B9;
text-decoration:none
}
.rst-versions .rst-badge-small{
display:none
}
.rst-versions .rst-current-version{
padding:12px;
background: hsla(173, 100%, 24%, 1) !important;
display:block;
text-align:right;
font-size:90%;
cursor:pointer;
color: white !important;
*zoom:1
}
.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{
display:table;content:""
}
.rst-versions .rst-current-version:after{
clear:both
}
.rst-versions .rst-current-version .fa{
color:#fcfcfc
}
.rst-versions .rst-current-version .fa-caret-down{
display: none;
}
.rst-versions.shift-up .rst-other-versions{
display:block
}
.rst-versions .rst-other-versions{
font-size:90%;
padding:12px;
color:gray;
display:none
}
.rst-versions .rst-other-versions hr{
display: none !important;
height: 0px !important;
border: 0px;
margin: 0px !important;
padding: 0px;
border-top: none !important;
}
.rst-versions .rst-other-versions dd{
display:inline-block;
margin:0
}
.rst-versions .rst-other-versions dd a{
display:inline-block;
padding: 1em 0em !important;
color:#fcfcfc;
font-size: .6rem !important;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: 80px;
}
.rst-versions .rst-other-versions dd a:hover{
font-size: .7rem !important;
font-weight: bold;
}
.rst-versions.rst-badge{
display: block !important;
width: 100px !important;
bottom: 0px !important;
right: 0px !important;
left:auto;
border:none;
text-align: center !important;
line-height: 0;
}
.rst-versions.rst-badge .icon-book{
display: none;
}
.rst-versions.rst-badge .fa-book{
display: none !important;
}
.rst-versions.rst-badge.shift-up .rst-current-version{
text-align: left !important;
}
.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{
display: none !important;
}
.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{
display: none !important;
}
.rst-versions.rst-badge .rst-current-version{
width: 70px !important;
height: 2.4rem !important;
line-height:2.4rem !important;
padding: 0px 5px !important;
display: inline-block !important;
font-size: .6rem !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
text-align: left !important;
}
@media screen and (max-width: 768px){
.rst-versions{
width:85%;
display:none
}
.rst-versions.shift{
display:block
}
}

43
docs/assets/versions.js Normal file
View File

@@ -0,0 +1,43 @@
setTimeout(function() {
const callbackName = 'callback_' + new Date().getTime();
window[callbackName] = function (response) {
const div = document.createElement('div');
div.innerHTML = response.html;
document.querySelector(".md-header-nav > .md-header-nav__title").appendChild(div);
const container = div.querySelector('.rst-versions');
var caret = document.createElement('div');
caret.innerHTML = "<i class='fa fa-caret-down dropdown-caret'></i>"
caret.classList.add('dropdown-caret')
div.querySelector('.rst-current-version').appendChild(caret);
div.querySelector('.rst-current-version').addEventListener('click', function() {
const classes = container.className.split(' ');
const index = classes.indexOf('shift-up');
if (index === -1) {
classes.push('shift-up');
} else {
classes.splice(index, 1);
}
container.className = classes.join(' ');
});
}
var CSSLink = document.createElement('link');
CSSLink.rel='stylesheet';
CSSLink.href = '/assets/versions.css';
document.getElementsByTagName('head')[0].appendChild(CSSLink);
var script = document.createElement('script');
script.src = 'https://argo-cd.readthedocs.io/_/api/v2/footer_html/?'+
'callback=' + callbackName + '&project=argo-cd&page=&theme=mkdocs&format=jsonp&docroot=docs&source_suffix=.md&version=' + (window['READTHEDOCS_DATA'] || { version: 'latest' }).version;
document.getElementsByTagName('head')[0].appendChild(script);
}, 0);
// VERSION WARNINGS
window.addEventListener("DOMContentLoaded", function() {
if ((window['READTHEDOCS_DATA']).version === "latest") {
document.querySelector("div[data-md-component=announce]").innerHTML = "<div id='announce-msg'>You are viewing the docs for an unreleased version of Argo CD, <a href='https://argo-cd.readthedocs.io/en/stable/'>click here to go to the latest stable version.</a></div>"
}
else if ((window['READTHEDOCS_DATA']).version !== "stable") {
document.querySelector("div[data-md-component=announce]").innerHTML = "<div id='announce-msg'>You are viewing the docs for a previous version of Argo CD, <a href='https://argo-cd.readthedocs.io/en/stable/'>click here to go to the latest stable version.</a></div>"
}
});

View File

@@ -36,6 +36,8 @@ and might fail. To avoid failed syncs use `ARGOCD_GIT_ATTEMPTS_COUNT` environmen
* `argocd_git_request_total` - Number of git requests. The metric provides two tags: `repo` - Git repo URL; `request_type` - `ls-remote` or `fetch`.
* `ARGOCD_ENABLE_GRPC_TIME_HISTOGRAM` (v1.8+) - environment variable that enables collecting RPC performance metrics. Enable it if you need to troubleshoot performance issue. Note: metric is expensive to both query and store!
### argocd-application-controller
**settings:**
@@ -82,6 +84,8 @@ spec:
value: "2"
```
* `ARGOCD_ENABLE_GRPC_TIME_HISTOGRAM` (v1.8+)- environment variable that enables collecting RPC performance metrics. Enable it if you need to troubleshoot performance issue. Note: metric is expensive to both query and store!
**metrics**
* `argocd_app_reconcile` - reports application reconciliation duration. Can be used to build reconciliation duration heat map to get high-level reconciliation performance picture.
@@ -92,6 +96,11 @@ non-preferred version and causes performance issues.
The `argocd-server` is stateless and probably least likely to cause issues. You might consider increasing number of replicas to 3 or more to ensure there is no downtime during upgrades.
**settings:**
* The `ARGOCD_GRPC_MAX_SIZE_MB` environment variable allows specifying the max size of the server response message in megabytes.
The default value is 200. You might need to increase for an Argo CD instance that manages 3000+ applications.
### argocd-dex-server, argocd-redis
The `argocd-dex-server` uses an in-memory database, and two or more instances would have inconsistent data. `argocd-redis` is pre-configured with the understanding of only three total redis servers/sentinels.

View File

@@ -28,7 +28,7 @@ Breaking down the permissions definition differs slightly between applications a
### RBAC Resources and Actions
Resources: `clusters`, `projects`, `applications`, `repositories`, `certificates`
Resources: `clusters`, `projects`, `applications`, `repositories`, `certificates`, `accounts`, `gpgkeys`
Actions: `get`, `create`, `update`, `delete`, `sync`, `override`, `action`

View File

@@ -0,0 +1,47 @@
# v1.7 to 1.8
## The argocd-application-controller converted to StatefulSet
The `argocd-application-controller` has been converted to StatefulSet. That means you need to manually delete `argocd-application-controller` Deployment after upgrading.
Similarly if you decided to rollback to v1.7 don't forget to delete `argocd-application-controller` StatefulSet.
## Health assessement of argoproj.io/Application CRD has been removed
The health assessement of `argoproj.io/Application` CRD has been removed (see [#3781](https://github.com/argoproj/argo-cd/issues/3781) for more information).
You might need to restore it if you are using app-of-apps pattern and orchestrating syncronization using sync waves. Add the following resource customization in
`argocd-cm` ConfigMap:
```yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
labels:
app.kubernetes.io/name: argocd-cm
app.kubernetes.io/part-of: argocd
data:
resource.customizations: |
argoproj.io/Application:
health.lua: |
hs = {}
hs.status = "Healthy"
hs.message = ""
if obj.status ~= nil then
if obj.status.health ~= nil then
hs.status = obj.status.health.status
hs.message = obj.status.health.message
end
end
return hs
```
## gRPC metrics are disabled by default
The gRPC metrics are not exposed by default by `argocd-server` and `argocd-repo-server` anymore. These metrics appear
to be too expensive so we've decided to disable them by default. Metrics can be enabled using
`ARGOCD_ENABLE_GRPC_TIME_HISTOGRAM=true` environment variable.
From here on you can follow the [regular upgrade process](./overview.md).

View File

@@ -37,6 +37,7 @@ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/<v
<hr/>
* [v1.7 to v1.8](./1.7-1.8.md)
* [v1.6 to v1.7](./1.6-1.7.md)
* [v1.5 to v1.6](./1.5-1.6.md)
* [v1.4 to v1.5](./1.4-1.5.md)

View File

@@ -263,3 +263,29 @@ For a simple case this can be:
oidc.config: |
requestedIDTokenClaims: {"groups": {"essential": true}}
```
### Configuring a custom logout URL for your OIDC provider
Optionally, if your OIDC provider exposes a logout API and you wish to configure a custom logout URL for the purposes of invalidating
any active session post logout, you can do so by specifying it as follows:
```yaml
oidc.config: |
name: example-OIDC-provider
issuer: https://example-OIDC-provider.com
clientID: xxxxxxxxx
clientSecret: xxxxxxxxx
requestedScopes: ["openid", "profile", "email", "groups"]
requestedIDTokenClaims: {"groups": {"essential": true}}
logoutURL: https://example-OIDC-provider.com/logout?id_token_hint={{token}}
```
By default, this would take the user to their OIDC provider's login page after logout. If you also wish to redirect the user back to Argo CD after logout, you can specify the logout URL as follows:
```yaml
...
logoutURL: https://example-OIDC-provider.com/logout?id_token_hint={{token}}&post_logout_redirect_uri={{logoutRedirectURL}}
```
You are not required to specify a logoutRedirectURL as this is automatically generated by ArgoCD as your base ArgoCD url + Rootpath
!!! note
The post logout redirect URI may need to be whitelisted against your OIDC provider's client settings for ArgoCD.

3
docs/requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
mkdocs-material
markdown_include
pygments==2.4

24
go.mod
View File

@@ -7,13 +7,13 @@ require (
github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 // indirect
github.com/alicebob/miniredis v2.5.0+incompatible
github.com/argoproj/gitops-engine v0.1.3-0.20201113084616-069a5e64fb79
github.com/argoproj/gitops-engine v0.2.2
github.com/argoproj/pkg v0.2.0
github.com/bombsimon/logrusr v1.0.0
github.com/casbin/casbin v1.9.1
github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 // indirect
github.com/coreos/go-oidc v2.1.0+incompatible
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect
github.com/dustin/go-humanize v1.0.0
github.com/evanphx/json-patch v4.9.0+incompatible
@@ -23,19 +23,19 @@ require (
github.com/go-openapi/loads v0.19.4
github.com/go-openapi/runtime v0.19.4
github.com/go-openapi/spec v0.19.3
github.com/go-redis/cache/v8 v8.0.0-beta.11
github.com/go-redis/redis/v8 v8.0.0-beta.9
github.com/go-redis/cache/v8 v8.2.1
github.com/go-redis/redis/v8 v8.3.2
github.com/gobuffalo/packr v1.11.0
github.com/gobwas/glob v0.2.3
github.com/gogits/go-gogs-client v0.0.0-20190616193657-5a05380e4bc2
github.com/gogo/protobuf v1.3.1
github.com/golang/protobuf v1.4.2
github.com/golang/protobuf v1.4.3
github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/google/go-cmp v0.5.1
github.com/google/go-jsonnet v0.16.0
github.com/google/go-cmp v0.5.2
github.com/google/go-jsonnet v0.17.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/google/uuid v1.1.1
github.com/gorilla/handlers v1.5.0
github.com/gorilla/handlers v1.5.1
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
@@ -43,6 +43,7 @@ require (
github.com/improbable-eng/grpc-web v0.0.0-20181111100011-16092bd1d58a
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/malexdev/utfutil v0.0.0-20180510171754-00c8d4a8e7a8 // indirect
github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf // indirect
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.9.1
@@ -57,11 +58,13 @@ require (
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.6.1
github.com/undefinedlabs/go-mpatch v1.0.6
github.com/vmihailenco/msgpack/v5 v5.1.0 // indirect
github.com/yuin/gopher-lua v0.0.0-20190115140932-732aa6820ec4
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/net v0.0.0-20200707034311-ab3426394381
golang.org/x/exp v0.0.0-20200821190819-94841d0725da // indirect
golang.org/x/net v0.0.0-20201024042810-be3efd7ff127
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
google.golang.org/grpc v1.29.1
gopkg.in/go-playground/webhooks.v5 v5.11.0
@@ -85,6 +88,7 @@ require (
replace (
github.com/golang/protobuf => github.com/golang/protobuf v1.4.2
github.com/gorilla/websocket => github.com/gorilla/websocket v1.4.2
github.com/grpc-ecosystem/grpc-gateway => github.com/grpc-ecosystem/grpc-gateway v1.12.2
github.com/improbable-eng/grpc-web => github.com/improbable-eng/grpc-web v0.0.0-20181111100011-16092bd1d58a

107
go.sum
View File

@@ -42,7 +42,6 @@ github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VY
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20200415212048-7901bc822317/go.mod h1:DF8FZRxMHMGv/vP2lQP6h+dYzzjpuRn24VeRiYn3qjQ=
github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
@@ -64,8 +63,6 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d h1:WtAMR0fPCOfK7TPGZ8ZpLLY18HRvL7XJ3xcs0wnREgo=
github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d/go.mod h1:WML6KOYjeU8N6YyusMjj2qRvaPNUEvrQvaxuFcMRFJY=
github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw=
github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
@@ -78,14 +75,12 @@ github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 h1:45bxf7AZMw
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI=
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
github.com/argoproj/gitops-engine v0.1.3-0.20201113084616-069a5e64fb79 h1:+VXKYb/FNTag/vnSoH9/Pey/kXK/jBKDyOtwRfnmMMw=
github.com/argoproj/gitops-engine v0.1.3-0.20201113084616-069a5e64fb79/go.mod h1:OxXp8YaT73rw9gEBnGBWg55af80nkV/uIjWCbJu1Nw0=
github.com/argoproj/gitops-engine v0.2.2 h1:islNwFTaHVVgx2pMvgXqINV93WPCVInWe64DlasFSx4=
github.com/argoproj/gitops-engine v0.2.2/go.mod h1:OxXp8YaT73rw9gEBnGBWg55af80nkV/uIjWCbJu1Nw0=
github.com/argoproj/pkg v0.2.0 h1:ETgC600kr8WcAi3MEVY5sA1H7H/u1/IysYOobwsZ8No=
github.com/argoproj/pkg v0.2.0/go.mod h1:F4TZgInLUEjzsWFB/BTJBsewoEy0ucnKSq6vmQiD/yc=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -99,7 +94,6 @@ github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7/go.mod h1:
github.com/aws/aws-sdk-go v1.6.10/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k=
github.com/aws/aws-sdk-go v1.28.2/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.33.16/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/benbjohnson/clock v1.0.0/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -167,6 +161,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -174,6 +170,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
@@ -291,11 +289,10 @@ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2K
github.com/go-openapi/validate v0.19.5 h1:QhCBKRYqZR+SKo4gl1lPhPahope8/RLt6EVgY8X80w0=
github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
github.com/go-redis/cache/v8 v8.0.0-beta.11 h1:6QdpDYemSFQ2yrDkDkEofHpXid6Mm3UctqaVUzxGdn8=
github.com/go-redis/cache/v8 v8.0.0-beta.11/go.mod h1:4wxD/neK+Uw+SteOR+AXtlyQYMBlI/D1u7UahfDCBAI=
github.com/go-redis/redis/v8 v8.0.0-beta.2/go.mod h1:o1M7JtsgfDYyv3o+gBn/jJ1LkqpnCrmil7PSppZGBak=
github.com/go-redis/redis/v8 v8.0.0-beta.9 h1:gcRNXZvs4PFi/mptho1D3hXant8JsbQKQxQ+0W3QGPw=
github.com/go-redis/redis/v8 v8.0.0-beta.9/go.mod h1:CCWlPAL3uftOyyBOGpJ+A0ps2WpG3+hqo14fVs5aQ6A=
github.com/go-redis/cache/v8 v8.2.1 h1:G4CtEQDT3JsiERPob1nUL/KTkiC317rAJvHx6GdWjiM=
github.com/go-redis/cache/v8 v8.2.1/go.mod h1:8PFGBZrRqG2nToSHw76mSsozxgSKrn3vsZerq/NJtt8=
github.com/go-redis/redis/v8 v8.3.2 h1:1bJscgN2yGtKLW6MsTRosa2LHyeq94j0hnNAgRZzj/M=
github.com/go-redis/redis/v8 v8.3.2/go.mod h1:jszGxBCez8QA1HWSmQxJO9Y82kNibbUmeYhKWrBejTU=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -326,8 +323,6 @@ github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho=
github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8=
github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=
@@ -341,10 +336,11 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-jsonnet v0.16.0 h1:Nb4EEOp+rdeGGyB1rQ5eisgSAqrTnhf9ip+X6lzZbY0=
github.com/google/go-jsonnet v0.16.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-jsonnet v0.17.0 h1:/9NIEfhK1NQRKl3sP2536b2+x5HnZMdql7x3yK/l8JY=
github.com/google/go-jsonnet v0.17.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -365,12 +361,11 @@ github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3i
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.5.0 h1:4wjo3sf9azi99c8hTmyaxp9y5S+pFszsy3pP0rAw/lw=
github.com/gorilla/handlers v1.5.0/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
@@ -425,8 +420,8 @@ github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.5 h1:7q6vHIqubShURwQz8cQK6yIe/xC3IF0Vm7TGfqjewrc=
github.com/klauspost/compress v1.10.5/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.1 h1:bPb7nMRdOZYDrpPMTA3EInUQrdgoBinqUuSwlGdKDdE=
github.com/klauspost/compress v1.11.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -487,12 +482,12 @@ github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mmcloughlin/avo v0.0.0-20200504053806-fa88270b07e4 h1:HqABfvSTSz0ipb7ArOwybHX8/5lSzn0eU7BDYiBU/XY=
github.com/mmcloughlin/avo v0.0.0-20200504053806-fa88270b07e4/go.mod h1:wqKykBG2QzQDJEzvRkcS8x6MiSJkF52hXZsXcjaB3ls=
github.com/moby/ipvs v1.0.1/go.mod h1:2pngiyseZbIKXNv7hsKj3O9UEz30c53MT9005gt2hxQ=
github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o=
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI=
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf h1:Un6PNx5oMK6CCwO3QTUyPiK2mtZnPrpDl5UnZ64eCkw=
github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -512,18 +507,26 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
@@ -535,7 +538,6 @@ github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/
github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.5.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g=
github.com/opencontainers/selinux v1.5.2/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g=
github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
@@ -654,11 +656,13 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17
github.com/vishvananda/netns v0.0.0-20200520041808-52d707b772fe/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94=
github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ=
github.com/vmihailenco/msgpack/v4 v4.3.11/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
github.com/vmihailenco/msgpack/v5 v5.0.0-alpha.2 h1:0jVpYJSRJzGY7m21n9V5uIkl7Zre64W8DR1dxEKX2g4=
github.com/vmihailenco/msgpack/v5 v5.0.0-alpha.2/go.mod h1:LDfrk4wJpSFwkzNOJxrCWiSm8c7Iqw/hXNPT2fzQfE8=
github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY=
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/vmihailenco/go-tinylfu v0.1.0 h1:wNwKigNq50gfiyQDPpseEuGK4TZtFyjduJSg0M6gBns=
github.com/vmihailenco/go-tinylfu v0.1.0/go.mod h1:qZbD6U3F10Sfuxyy4c5wMq5CM4/t5I3eJJS9yMQoXU0=
github.com/vmihailenco/msgpack/v5 v5.0.0-beta.5/go.mod h1:MPECSZPg8yittBek5Gq2MhEDJpB9FrbSzQOSWmJm38A=
github.com/vmihailenco/msgpack/v5 v5.1.0 h1:+od5YbEXxW95SPlW6beocmt8nOtlh83zqat5Ip9Hwdc=
github.com/vmihailenco/msgpack/v5 v5.1.0/go.mod h1:C5gboKD0TJPqWDTVTtrQNfRbiBwHZGo8UTqP/9/XvLI=
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
@@ -679,14 +683,12 @@ go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v0.5.0/go.mod h1:jzBIgIzK43Iu1BpDAXwqOd6UPsSAk+ewVZ5ofSXw4Ek=
go.opentelemetry.io/otel v0.11.0 h1:IN2tzQa9Gc4ZVKnTaMbPVcHjvzOdg5n9QfnmlqiET7E=
go.opentelemetry.io/otel v0.11.0/go.mod h1:G8UCk+KooF2HLkgo8RHX9epABH/aRGYET7gQOqBVdB0=
go.opentelemetry.io/otel v0.13.0 h1:2isEnyzjjJZq6r2EKMsFj4TxiQiexsM04AVhwbR/oBA=
go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/arch v0.0.0-20190909030613-46d78d1859ac/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -755,18 +757,20 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201024042810-be3efd7ff127 h1:pZPp9+iYUqwYKLjht0SDBbRCRK/9gAXDy7pz5fRDpjo=
golang.org/x/net v0.0.0-20201024042810-be3efd7ff127/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -780,6 +784,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -803,10 +809,10 @@ golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -823,9 +829,13 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8=
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -866,8 +876,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200616133436-c1934b75d054 h1:HHeAlu5H9b71C+Fx0K+1dGgVFN1DM1/wz4aoGOA5qS8=
golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -892,8 +900,8 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -903,7 +911,6 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
@@ -914,6 +921,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -949,7 +958,6 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -957,6 +965,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -2,7 +2,7 @@
# The checksum of this file is used as cache key in our integration toolchain
#
helm2_version=2.17.0
helm3_version=3.4.0
helm3_version=3.4.1
jq_version=1.6
ksonnet_version=0.13.1
kubectl_version=1.17.8

View File

@@ -42,3 +42,18 @@ spec:
initialDelaySeconds: 5
periodSeconds: 10
serviceAccountName: argocd-application-controller
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: argocd-application-controller
topologyKey: kubernetes.io/hostname
- weight: 5
podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname

View File

@@ -26,7 +26,7 @@ spec:
name: static-files
containers:
- name: dex
image: quay.io/dexidp/dex:v2.25.0
image: ghcr.io/dexidp/dex:v2.27.0
imagePullPolicy: Always
command: [/shared/argocd-util, rundex]
ports:
@@ -39,3 +39,12 @@ spec:
volumes:
- emptyDir: {}
name: static-files
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 5
podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname

View File

@@ -5,7 +5,7 @@ kind: Kustomization
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: latest
newTag: v1.8.6
resources:
- ./application-controller
- ./dex

View File

@@ -32,3 +32,18 @@ spec:
- "no"
ports:
- containerPort: 6379
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: argocd-redis
topologyKey: kubernetes.io/hostname
- weight: 5
podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname

View File

@@ -28,9 +28,17 @@ spec:
ports:
- containerPort: 8081
- containerPort: 8084
livenessProbe:
httpGet:
path: /healthz?full=true
port: 8084
initialDelaySeconds: 30
periodSeconds: 5
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 8081
httpGet:
path: /healthz
port: 8084
initialDelaySeconds: 5
periodSeconds: 10
volumeMounts:
@@ -54,3 +62,18 @@ spec:
name: argocd-gpg-keys-cm
- name: gpg-keyring
emptyDir: {}
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: argocd-repo-server
topologyKey: kubernetes.io/hostname
- weight: 5
podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname

View File

@@ -29,6 +29,12 @@ spec:
ports:
- containerPort: 8080
- containerPort: 8083
livenessProbe:
httpGet:
path: /healthz?full=true
port: 8080
initialDelaySeconds: 3
periodSeconds: 30
readinessProbe:
httpGet:
path: /healthz
@@ -44,3 +50,18 @@ spec:
- name: tls-certs
configMap:
name: argocd-tls-certs-cm
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: argocd-server
topologyKey: kubernetes.io/hostname
- weight: 5
podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname

View File

@@ -11,7 +11,7 @@ patchesStrategicMerge:
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: latest
newTag: v1.8.6
resources:
- ../../base/application-controller
- ../../base/dex

View File

@@ -2835,11 +2835,20 @@ spec:
labels:
app.kubernetes.io/name: argocd-dex-server
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname
weight: 5
containers:
- command:
- /shared/argocd-util
- rundex
image: quay.io/dexidp/dex:v2.25.0
image: ghcr.io/dexidp/dex:v2.27.0
imagePullPolicy: Always
name: dex
ports:
@@ -2855,7 +2864,7 @@ spec:
- -n
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v1.8.6
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2993,17 +3002,25 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis-ha-haproxy:6379
image: argoproj/argocd:latest
image: argoproj/argocd:v1.8.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz?full=true
port: 8084
initialDelaySeconds: 30
periodSeconds: 5
name: argocd-repo-server
ports:
- containerPort: 8081
- containerPort: 8084
readinessProbe:
httpGet:
path: /healthz
port: 8084
initialDelaySeconds: 5
periodSeconds: 10
tcpSocket:
port: 8081
volumeMounts:
- mountPath: /app/config/ssh
name: ssh-known-hosts
@@ -3067,8 +3084,14 @@ spec:
env:
- name: ARGOCD_API_SERVER_REPLICAS
value: "2"
image: argoproj/argocd:latest
image: argoproj/argocd:v1.8.6
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz?full=true
port: 8080
initialDelaySeconds: 3
periodSeconds: 30
name: argocd-server
ports:
- containerPort: 8080
@@ -3113,6 +3136,21 @@ spec:
labels:
app.kubernetes.io/name: argocd-application-controller
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: argocd-application-controller
topologyKey: kubernetes.io/hostname
weight: 100
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname
weight: 5
containers:
- command:
- argocd-application-controller
@@ -3122,7 +3160,7 @@ spec:
- "10"
- --redis
- argocd-redis-ha-haproxy:6379
image: argoproj/argocd:latest
image: argoproj/argocd:v1.8.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -2750,11 +2750,20 @@ spec:
labels:
app.kubernetes.io/name: argocd-dex-server
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname
weight: 5
containers:
- command:
- /shared/argocd-util
- rundex
image: quay.io/dexidp/dex:v2.25.0
image: ghcr.io/dexidp/dex:v2.27.0
imagePullPolicy: Always
name: dex
ports:
@@ -2770,7 +2779,7 @@ spec:
- -n
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v1.8.6
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2908,17 +2917,25 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis-ha-haproxy:6379
image: argoproj/argocd:latest
image: argoproj/argocd:v1.8.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz?full=true
port: 8084
initialDelaySeconds: 30
periodSeconds: 5
name: argocd-repo-server
ports:
- containerPort: 8081
- containerPort: 8084
readinessProbe:
httpGet:
path: /healthz
port: 8084
initialDelaySeconds: 5
periodSeconds: 10
tcpSocket:
port: 8081
volumeMounts:
- mountPath: /app/config/ssh
name: ssh-known-hosts
@@ -2982,8 +2999,14 @@ spec:
env:
- name: ARGOCD_API_SERVER_REPLICAS
value: "2"
image: argoproj/argocd:latest
image: argoproj/argocd:v1.8.6
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz?full=true
port: 8080
initialDelaySeconds: 3
periodSeconds: 30
name: argocd-server
ports:
- containerPort: 8080
@@ -3028,6 +3051,21 @@ spec:
labels:
app.kubernetes.io/name: argocd-application-controller
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: argocd-application-controller
topologyKey: kubernetes.io/hostname
weight: 100
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname
weight: 5
containers:
- command:
- argocd-application-controller
@@ -3037,7 +3075,7 @@ spec:
- "10"
- --redis
- argocd-redis-ha-haproxy:6379
image: argoproj/argocd:latest
image: argoproj/argocd:v1.8.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -2424,11 +2424,20 @@ spec:
labels:
app.kubernetes.io/name: argocd-dex-server
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname
weight: 5
containers:
- command:
- /shared/argocd-util
- rundex
image: quay.io/dexidp/dex:v2.25.0
image: ghcr.io/dexidp/dex:v2.27.0
imagePullPolicy: Always
name: dex
ports:
@@ -2444,7 +2453,7 @@ spec:
- -n
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v1.8.6
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2472,6 +2481,21 @@ spec:
labels:
app.kubernetes.io/name: argocd-redis
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: argocd-redis
topologyKey: kubernetes.io/hostname
weight: 100
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname
weight: 5
containers:
- args:
- --save
@@ -2507,6 +2531,21 @@ spec:
labels:
app.kubernetes.io/name: argocd-repo-server
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: argocd-repo-server
topologyKey: kubernetes.io/hostname
weight: 100
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname
weight: 5
automountServiceAccountToken: false
containers:
- command:
@@ -2514,17 +2553,25 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis:6379
image: argoproj/argocd:latest
image: argoproj/argocd:v1.8.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz?full=true
port: 8084
initialDelaySeconds: 30
periodSeconds: 5
name: argocd-repo-server
ports:
- containerPort: 8081
- containerPort: 8084
readinessProbe:
httpGet:
path: /healthz
port: 8084
initialDelaySeconds: 5
periodSeconds: 10
tcpSocket:
port: 8081
volumeMounts:
- mountPath: /app/config/ssh
name: ssh-known-hosts
@@ -2564,13 +2611,34 @@ spec:
labels:
app.kubernetes.io/name: argocd-server
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: argocd-server
topologyKey: kubernetes.io/hostname
weight: 100
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname
weight: 5
containers:
- command:
- argocd-server
- --staticassets
- /shared/app
image: argoproj/argocd:latest
image: argoproj/argocd:v1.8.6
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz?full=true
port: 8080
initialDelaySeconds: 3
periodSeconds: 30
name: argocd-server
ports:
- containerPort: 8080
@@ -2616,6 +2684,21 @@ spec:
labels:
app.kubernetes.io/name: argocd-application-controller
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: argocd-application-controller
topologyKey: kubernetes.io/hostname
weight: 100
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname
weight: 5
containers:
- command:
- argocd-application-controller
@@ -2623,7 +2706,7 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:latest
image: argoproj/argocd:v1.8.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -2339,11 +2339,20 @@ spec:
labels:
app.kubernetes.io/name: argocd-dex-server
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname
weight: 5
containers:
- command:
- /shared/argocd-util
- rundex
image: quay.io/dexidp/dex:v2.25.0
image: ghcr.io/dexidp/dex:v2.27.0
imagePullPolicy: Always
name: dex
ports:
@@ -2359,7 +2368,7 @@ spec:
- -n
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v1.8.6
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2387,6 +2396,21 @@ spec:
labels:
app.kubernetes.io/name: argocd-redis
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: argocd-redis
topologyKey: kubernetes.io/hostname
weight: 100
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname
weight: 5
containers:
- args:
- --save
@@ -2422,6 +2446,21 @@ spec:
labels:
app.kubernetes.io/name: argocd-repo-server
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: argocd-repo-server
topologyKey: kubernetes.io/hostname
weight: 100
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname
weight: 5
automountServiceAccountToken: false
containers:
- command:
@@ -2429,17 +2468,25 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis:6379
image: argoproj/argocd:latest
image: argoproj/argocd:v1.8.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz?full=true
port: 8084
initialDelaySeconds: 30
periodSeconds: 5
name: argocd-repo-server
ports:
- containerPort: 8081
- containerPort: 8084
readinessProbe:
httpGet:
path: /healthz
port: 8084
initialDelaySeconds: 5
periodSeconds: 10
tcpSocket:
port: 8081
volumeMounts:
- mountPath: /app/config/ssh
name: ssh-known-hosts
@@ -2479,13 +2526,34 @@ spec:
labels:
app.kubernetes.io/name: argocd-server
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: argocd-server
topologyKey: kubernetes.io/hostname
weight: 100
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname
weight: 5
containers:
- command:
- argocd-server
- --staticassets
- /shared/app
image: argoproj/argocd:latest
image: argoproj/argocd:v1.8.6
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz?full=true
port: 8080
initialDelaySeconds: 3
periodSeconds: 30
name: argocd-server
ports:
- containerPort: 8080
@@ -2531,6 +2599,21 @@ spec:
labels:
app.kubernetes.io/name: argocd-application-controller
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: argocd-application-controller
topologyKey: kubernetes.io/hostname
weight: 100
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname
weight: 5
containers:
- command:
- argocd-application-controller
@@ -2538,7 +2621,7 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:latest
image: argoproj/argocd:v1.8.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -9,6 +9,12 @@ theme:
text: 'Work Sans'
logo: 'assets/logo.png'
favicon: 'assets/favicon.png'
# language: en-custom
custom_dir: overrides
extra_javascript:
- assets/versions.js
extra_css:
- assets/versions.css
google_analytics:
- 'UA-105170809-2'
- 'auto'

View File

@@ -0,0 +1,3 @@
{% macro t(key) %}{{ {
"toc.title": "Table of Contents"
}[key] }}{% endmacro %}

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"io/ioutil"
"math"
"net"
"net/http"
"os"
@@ -16,7 +17,7 @@ import (
"time"
"github.com/coreos/go-oidc"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/v4"
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"google.golang.org/grpc"
@@ -39,6 +40,7 @@ import (
versionpkg "github.com/argoproj/argo-cd/pkg/apiclient/version"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/env"
grpc_util "github.com/argoproj/argo-cd/util/grpc"
argoio "github.com/argoproj/argo-cd/util/io"
"github.com/argoproj/argo-cd/util/kube"
@@ -53,8 +55,13 @@ const (
EnvArgoCDServer = "ARGOCD_SERVER"
// EnvArgoCDAuthToken is the environment variable to look for an Argo CD auth token
EnvArgoCDAuthToken = "ARGOCD_AUTH_TOKEN"
// EnvArgoCDgRPCMaxSizeMB is the environment variable to look for a max gRPC message size
EnvArgoCDgRPCMaxSizeMB = "ARGOCD_GRPC_MAX_SIZE_MB"
)
var (
// MaxGRPCMessageSize contains max grpc message size
MaxGRPCMessageSize = 100 * 1024 * 1024
MaxGRPCMessageSize = env.ParseNumFromEnv(EnvArgoCDgRPCMaxSizeMB, 200, 0, math.MaxInt32) * 1024 * 1024
)
// Client defines an interface for interaction with an Argo CD server.
@@ -321,14 +328,14 @@ func (c *client) refreshAuthToken(localCfg *localconfig.LocalConfig, ctxName, co
return err
}
parser := &jwt.Parser{
SkipClaimsValidation: true,
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation(), jwt.WithoutAudienceValidation()),
}
var claims jwt.StandardClaims
_, _, err = parser.ParseUnverified(configCtx.User.AuthToken, &claims)
if err != nil {
return err
}
if claims.Valid() == nil {
if claims.Valid(parser.ValidationHelper) == nil {
// token is still valid
return nil
}

View File

@@ -170,10 +170,7 @@ func (a *ApplicationSource) IsHelmOci() bool {
if a.Chart == "" {
return false
}
if _, _, ok := helm.IsHelmOci(a.Chart); ok {
return true
}
return false
return helm.IsHelmOciChart(a.Chart)
}
func (a *ApplicationSource) IsZero() bool {
@@ -946,6 +943,17 @@ type ApplicationTree struct {
OrphanedNodes []ResourceNode `json:"orphanedNodes,omitempty" protobuf:"bytes,2,rep,name=orphanedNodes"`
}
// Normalize sorts application tree nodes and hosts. The persistent order allows to
// effectively compare previously cached app tree and allows to unnecessary Redis requests.
func (t *ApplicationTree) Normalize() {
sort.Slice(t.Nodes, func(i, j int) bool {
return t.Nodes[i].FullName() < t.Nodes[j].FullName()
})
sort.Slice(t.OrphanedNodes, func(i, j int) bool {
return t.OrphanedNodes[i].FullName() < t.OrphanedNodes[j].FullName()
})
}
type ApplicationSummary struct {
// ExternalURLs holds all external URLs of application child resources.
ExternalURLs []string `json:"externalURLs,omitempty" protobuf:"bytes,1,opt,name=externalURLs"`
@@ -1014,6 +1022,11 @@ type ResourceNode struct {
CreatedAt *metav1.Time `json:"createdAt,omitempty" protobuf:"bytes,8,opt,name=createdAt"`
}
// FullName returns node full name
func (n *ResourceNode) FullName() string {
return fmt.Sprintf("%s/%s/%s/%s", n.Group, n.Kind, n.Namespace, n.Name)
}
func (n *ResourceNode) GroupKindVersion() schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: n.Group,
@@ -1059,6 +1072,11 @@ type ResourceDiff struct {
PredictedLiveState string `json:"predictedLiveState,omitempty" protobuf:"bytes,10,opt,name=predictedLiveState"`
}
// FullName returns full name of a node that was used for diffing
func (r *ResourceDiff) FullName() string {
return fmt.Sprintf("%s/%s/%s/%s", r.Group, r.Kind, r.Namespace, r.Name)
}
// ConnectionStatus represents connection status
type ConnectionStatus = string
@@ -2398,6 +2416,17 @@ func (source *ApplicationSource) ExplicitType() (*ApplicationSourceType, error)
// Equals compares two instances of ApplicationDestination and return true if instances are equal.
func (dest ApplicationDestination) Equals(other ApplicationDestination) bool {
// ignore destination cluster name and isServerInferred fields during comparison
// since server URL is inferred from cluster name
if dest.isServerInferred {
dest.Server = ""
dest.isServerInferred = false
}
if other.isServerInferred {
other.Server = ""
other.isServerInferred = false
}
return reflect.DeepEqual(dest, other)
}
@@ -2699,8 +2728,11 @@ func (proj *AppProject) NormalizeJWTTokens() bool {
}
func syncJWTTokenBetweenStatusAndSpec(proj *AppProject) bool {
existingRole := map[string]bool{}
needSync := false
for roleIndex, role := range proj.Spec.Roles {
existingRole[role.Name] = true
tokensInSpec := role.JWTTokens
tokensInStatus := []JWTToken{}
if proj.Status.JWTTokensByRole == nil {
@@ -2723,8 +2755,16 @@ func syncJWTTokenBetweenStatusAndSpec(proj *AppProject) bool {
proj.Spec.Roles[roleIndex].JWTTokens = tokens
proj.Status.JWTTokensByRole[role.Name] = JWTTokens{Items: tokens}
}
if proj.Status.JWTTokensByRole != nil {
for role := range proj.Status.JWTTokensByRole {
if !existingRole[role] {
delete(proj.Status.JWTTokensByRole, role)
needSync = true
}
}
}
return needSync
}

View File

@@ -429,6 +429,21 @@ func TestAppDestinationEquality(t *testing.T) {
assert.False(t, left.Equals(*right))
}
func TestAppDestinationEquality_InferredServerURL(t *testing.T) {
left := ApplicationDestination{
Name: "in-cluster",
Namespace: "default",
}
right := ApplicationDestination{
Name: "in-cluster",
Server: "https://kubernetes.default.svc",
Namespace: "default",
isServerInferred: true,
}
assert.True(t, left.Equals(right))
assert.True(t, right.Equals(left))
}
func TestAppProjectSpec_DestinationClusters(t *testing.T) {
tests := []struct {
name string

View File

@@ -30,13 +30,21 @@ type clientSet struct {
}
func (c *clientSet) NewRepoServerClient() (io.Closer, RepoServerServiceClient, error) {
conn, err := NewConnection(c.address, c.timeoutSeconds)
if err != nil {
return nil, nil, err
}
return conn, NewRepoServerServiceClient(conn), nil
}
func NewConnection(address string, timeoutSeconds int) (*grpc.ClientConn, error) {
retryOpts := []grpc_retry.CallOption{
grpc_retry.WithMax(3),
grpc_retry.WithBackoff(grpc_retry.BackoffLinear(1000 * time.Millisecond)),
}
unaryInterceptors := []grpc.UnaryClientInterceptor{grpc_retry.UnaryClientInterceptor(retryOpts...)}
if c.timeoutSeconds > 0 {
unaryInterceptors = append(unaryInterceptors, argogrpc.WithTimeout(time.Duration(c.timeoutSeconds)*time.Second))
if timeoutSeconds > 0 {
unaryInterceptors = append(unaryInterceptors, argogrpc.WithTimeout(time.Duration(timeoutSeconds)*time.Second))
}
opts := []grpc.DialOption{
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})),
@@ -45,12 +53,12 @@ func (c *clientSet) NewRepoServerClient() (io.Closer, RepoServerServiceClient, e
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxGRPCMessageSize), grpc.MaxCallSendMsgSize(MaxGRPCMessageSize)),
}
conn, err := grpc.Dial(c.address, opts...)
conn, err := grpc.Dial(address, opts...)
if err != nil {
log.Errorf("Unable to connect to repository service with address %s", c.address)
return nil, nil, err
log.Errorf("Unable to connect to repository service with address %s", address)
return nil, err
}
return conn, NewRepoServerServiceClient(conn), nil
return conn, nil
}
// NewRepoServerClientset creates new instance of repo server Clientset

View File

@@ -127,11 +127,11 @@ func appDetailsCacheKey(revision string, appSrc *appv1.ApplicationSource) string
return fmt.Sprintf("appdetails|%s|%d", revision, appSourceKey(appSrc))
}
func (c *Cache) GetAppDetails(revision string, appSrc *appv1.ApplicationSource, res interface{}) error {
func (c *Cache) GetAppDetails(revision string, appSrc *appv1.ApplicationSource, res *apiclient.RepoAppDetailsResponse) error {
return c.cache.GetItem(appDetailsCacheKey(revision, appSrc), res)
}
func (c *Cache) SetAppDetails(revision string, appSrc *appv1.ApplicationSource, res interface{}) error {
func (c *Cache) SetAppDetails(revision string, appSrc *appv1.ApplicationSource, res *apiclient.RepoAppDetailsResponse) error {
return c.cache.SetItem(appDetailsCacheKey(revision, appSrc), res, c.repoCacheExpiration, res == nil)
}

View File

@@ -164,6 +164,23 @@ type operationSettings struct {
allowConcurrent bool
}
// operationContext contains request values which are generated by runRepoOperation (on demand) by a call to the
// provided operationContextSrc function.
type operationContext struct {
// application path or helm chart path
appPath string
// output of 'git verify-(tag/commit)', if signature verifiction is enabled (otherwise "")
verificationResult string
}
// The 'operation' function parameter of 'runRepoOperation' may call this function to retrieve
// the appPath or GPG verificationResult.
// Failure to generate either of these values will return an error which may be cached by
// the calling function (for example, 'runManifestGen')
type operationContextSrc = func() (*operationContext, error)
// runRepoOperation downloads either git folder or helm chart and executes specified operation
// - Returns a value from the cache if present (by calling getCached(...)); if no value is present, the
// provide operation(...) is called. The specific return type of this function is determined by the
@@ -175,13 +192,12 @@ func (s *Service) runRepoOperation(
source *v1alpha1.ApplicationSource,
verifyCommit bool,
getCached func(cacheKey string, firstInvocation bool) (bool, interface{}, error),
operation func(appPath, repoRoot, commitSHA, cacheKey, verifyResult string) (interface{}, error),
operation func(repoRoot, commitSHA, cacheKey string, ctxSrc operationContextSrc) (interface{}, error),
settings operationSettings) (interface{}, error) {
var gitClient git.Client
var helmClient helm.Client
var err error
var signature string
revision = textutils.FirstNonEmpty(revision, source.TargetRevision)
if source.IsHelm() {
helmClient, revision, err = s.newHelmClientResolveRevision(repo, revision, source.Chart)
@@ -229,7 +245,9 @@ func (s *Service) runRepoOperation(
return nil, err
}
defer io.Close(closer)
return operation(chartPath, chartPath, revision, revision, "")
return operation(chartPath, revision, revision, func() (*operationContext, error) {
return &operationContext{chartPath, ""}, nil
})
} else {
closer, err := s.repoLock.Lock(gitClient.Root(), revision, settings.allowConcurrent, func() error {
return checkoutRevision(gitClient, revision)
@@ -253,19 +271,22 @@ func (s *Service) runRepoOperation(
return obj, err
}
}
if verifyCommit {
signature, err = gitClient.VerifyCommitSignature(revision)
// Here commitSHA refers to the SHA of the actual commit, whereas revision refers to the branch/tag name etc
// We use the commitSHA to generate manifests and store them in cache, and revision to retrieve them from cache
return operation(gitClient.Root(), commitSHA, revision, func() (*operationContext, error) {
var signature string
if verifyCommit {
signature, err = gitClient.VerifyCommitSignature(revision)
if err != nil {
return nil, err
}
}
appPath, err := argopath.Path(gitClient.Root(), source.Path)
if err != nil {
return nil, err
}
}
appPath, err := argopath.Path(gitClient.Root(), source.Path)
if err != nil {
return nil, err
}
// Here commitSHA refers to the SHA of the actual commit, whereas revision refers to the branch/tag name etc
// We use the commitSHA to generate manifests and store them in cache, and revision to retrieve them from cache
return operation(appPath, gitClient.Root(), commitSHA, revision, signature)
return &operationContext{appPath, signature}, nil
})
}
}
@@ -273,8 +294,8 @@ func (s *Service) GenerateManifest(ctx context.Context, q *apiclient.ManifestReq
resultUncast, err := s.runRepoOperation(ctx, q.Revision, q.Repo, q.ApplicationSource, q.VerifySignature,
func(cacheKey string, firstInvocation bool) (bool, interface{}, error) {
return s.getManifestCacheEntry(cacheKey, q, firstInvocation)
}, func(appPath, repoRoot, commitSHA, cacheKey, verifyResult string) (interface{}, error) {
return s.runManifestGen(appPath, repoRoot, commitSHA, cacheKey, verifyResult, q)
}, func(repoRoot, commitSHA, cacheKey string, ctxSrc operationContextSrc) (interface{}, error) {
return s.runManifestGen(repoRoot, commitSHA, cacheKey, ctxSrc, q)
}, operationSettings{sem: s.parallelismLimitSemaphore, noCache: q.NoCache, allowConcurrent: q.ApplicationSource.AllowsConcurrentProcessing()})
result, ok := resultUncast.(*apiclient.ManifestResponse)
if result != nil && !ok {
@@ -289,8 +310,12 @@ func (s *Service) GenerateManifest(ctx context.Context, q *apiclient.ManifestReq
// - or, the cache does contain a value for this key, but it is an expired manifest generation entry
// - or, NoCache is true
// Returns a ManifestResponse, or an error, but not both
func (s *Service) runManifestGen(appPath, repoRoot, commitSHA, cacheKey, verifyResult string, q *apiclient.ManifestRequest) (interface{}, error) {
manifestGenResult, err := GenerateManifests(appPath, repoRoot, commitSHA, q, false)
func (s *Service) runManifestGen(repoRoot, commitSHA, cacheKey string, ctxSrc operationContextSrc, q *apiclient.ManifestRequest) (interface{}, error) {
var manifestGenResult *apiclient.ManifestResponse
ctx, err := ctxSrc()
if err == nil {
manifestGenResult, err = GenerateManifests(ctx.appPath, repoRoot, commitSHA, q, false)
}
if err != nil {
// If manifest generation error caching is enabled
@@ -332,7 +357,7 @@ func (s *Service) runManifestGen(appPath, repoRoot, commitSHA, cacheKey, verifyR
MostRecentError: "",
}
manifestGenResult.Revision = commitSHA
manifestGenResult.VerifyResult = verifyResult
manifestGenResult.VerifyResult = ctx.verificationResult
err = s.cache.SetManifests(cacheKey, q.ApplicationSource, q.Namespace, q.AppLabelKey, q.AppLabelValue, &manifestGenCacheEntry)
if err != nil {
log.Warnf("manifest cache set error %s/%s: %v", q.ApplicationSource.String(), cacheKey, err)
@@ -742,7 +767,7 @@ func mergeSourceParameters(source *v1alpha1.ApplicationSource, path string) erro
func GetAppSourceType(source *v1alpha1.ApplicationSource, path string) (v1alpha1.ApplicationSourceType, error) {
err := mergeSourceParameters(source, path)
if err != nil {
return "", fmt.Errorf("error while parsing .argocd-app.yaml: %v", err)
return "", fmt.Errorf("error while parsing .argocd-source.yaml: %v", err)
}
appSourceType, err := source.ExplicitType()
@@ -837,23 +862,12 @@ func findManifests(appPath string, repoRoot string, env *v1alpha1.Env, directory
return nil
}
out, err := utfutil.ReadFile(path, utfutil.UTF8)
if err != nil {
return err
}
if strings.HasSuffix(f.Name(), ".json") {
var obj unstructured.Unstructured
err = json.Unmarshal(out, &obj)
if err != nil {
return status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", f.Name(), err)
}
objs = append(objs, &obj)
} else if strings.HasSuffix(f.Name(), ".jsonnet") {
if strings.HasSuffix(f.Name(), ".jsonnet") {
vm, err := makeJsonnetVm(appPath, repoRoot, directory.Jsonnet, env)
if err != nil {
return err
}
jsonStr, err := vm.EvaluateSnippet(path, string(out))
jsonStr, err := vm.EvaluateFile(path)
if err != nil {
return status.Errorf(codes.FailedPrecondition, "Failed to evaluate jsonnet %q: %v", f.Name(), err)
}
@@ -872,24 +886,37 @@ func findManifests(appPath string, repoRoot string, env *v1alpha1.Env, directory
objs = append(objs, &jsonObj)
}
} else {
yamlObjs, err := kube.SplitYAML(out)
out, err := utfutil.ReadFile(path, utfutil.UTF8)
if err != nil {
if len(yamlObjs) > 0 {
// If we get here, we had a multiple objects in a single YAML file which had some
// valid k8s objects, but errors parsing others (within the same file). It's very
// likely the user messed up a portion of the YAML, so report on that.
return status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", f.Name(), err)
}
// Otherwise, let's see if it looks like a resource, if yes, we return error
if bytes.Contains(out, []byte("apiVersion:")) &&
bytes.Contains(out, []byte("kind:")) &&
bytes.Contains(out, []byte("metadata:")) {
return status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", f.Name(), err)
}
// Otherwise, it might be a unrelated YAML file which we will ignore
return nil
return err
}
if strings.HasSuffix(f.Name(), ".json") {
var obj unstructured.Unstructured
err = json.Unmarshal(out, &obj)
if err != nil {
return status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", f.Name(), err)
}
objs = append(objs, &obj)
} else {
yamlObjs, err := kube.SplitYAML(out)
if err != nil {
if len(yamlObjs) > 0 {
// If we get here, we had a multiple objects in a single YAML file which had some
// valid k8s objects, but errors parsing others (within the same file). It's very
// likely the user messed up a portion of the YAML, so report on that.
return status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", f.Name(), err)
}
// Otherwise, let's see if it looks like a resource, if yes, we return error
if bytes.Contains(out, []byte("apiVersion:")) &&
bytes.Contains(out, []byte("kind:")) &&
bytes.Contains(out, []byte("metadata:")) {
return status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", f.Name(), err)
}
// Otherwise, it might be a unrelated YAML file which we will ignore
return nil
}
objs = append(objs, yamlObjs...)
}
objs = append(objs, yamlObjs...)
}
return nil
})
@@ -999,7 +1026,7 @@ func (s *Service) GetAppDetails(ctx context.Context, q *apiclient.RepoServerAppD
getCached := func(revision string, _ bool) (bool, interface{}, error) {
res := &apiclient.RepoAppDetailsResponse{}
err := s.cache.GetAppDetails(revision, q.Source, &res)
err := s.cache.GetAppDetails(revision, q.Source, res)
if err == nil {
log.Infof("app details cache hit: %s/%s", revision, q.Source.Path)
return true, res, nil
@@ -1014,7 +1041,12 @@ func (s *Service) GetAppDetails(ctx context.Context, q *apiclient.RepoServerAppD
}
resultUncast, err := s.runRepoOperation(ctx, q.Source.TargetRevision, q.Repo, q.Source, false, getCached, func(appPath, repoRoot, commitSHA, revision, verifyResult string) (interface{}, error) {
resultUncast, err := s.runRepoOperation(ctx, q.Source.TargetRevision, q.Repo, q.Source, false, getCached, func(repoRoot, commitSHA, revision string, ctxSrc operationContextSrc) (interface{}, error) {
ctx, err := ctxSrc()
if err != nil {
return nil, err
}
appPath := ctx.appPath
res := &apiclient.RepoAppDetailsResponse{}
appSourceType, err := GetAppSourceType(q.Source, appPath)
@@ -1245,10 +1277,13 @@ func (s *Service) newClientResolveRevision(repo *v1alpha1.Repository, revision s
}
func (s *Service) newHelmClientResolveRevision(repo *v1alpha1.Repository, revision string, chart string) (helm.Client, string, error) {
helmClient := s.newHelmClient(repo.Repo, repo.GetHelmCreds(), repo.EnableOCI)
helmClient := s.newHelmClient(repo.Repo, repo.GetHelmCreds(), repo.EnableOCI || helm.IsHelmOciChart(chart))
if helm.IsVersion(revision) {
return helmClient, revision, nil
}
if repo.EnableOCI {
return nil, "", errors.New("OCI helm registers don't support semver ranges. Exact revision must be specified.")
}
constraints, err := semver.NewConstraint(revision)
if err != nil {
return nil, "", fmt.Errorf("invalid revision '%s': %v", revision, err)

View File

@@ -2,7 +2,19 @@ package reposerver
import (
"crypto/tls"
"os"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/reflection"
"github.com/argoproj/argo-cd/common"
versionpkg "github.com/argoproj/argo-cd/pkg/apiclient/version"
"github.com/argoproj/argo-cd/reposerver/apiclient"
reposervercache "github.com/argoproj/argo-cd/reposerver/cache"
@@ -11,14 +23,6 @@ import (
"github.com/argoproj/argo-cd/server/version"
grpc_util "github.com/argoproj/argo-cd/util/grpc"
tlsutil "github.com/argoproj/argo-cd/util/tls"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/reflection"
)
// ArgoCDRepoServer is the repo server implementation
@@ -49,7 +53,9 @@ func NewServer(metricsServer *metrics.MetricsServer, cache *reposervercache.Cach
tlsConfig := &tls.Config{Certificates: []tls.Certificate{*cert}}
tlsConfCustomizer(tlsConfig)
grpc_prometheus.EnableHandlingTimeHistogram()
if os.Getenv(common.EnvEnableGRPCTimeHistogramEnv) == "true" {
grpc_prometheus.EnableHandlingTimeHistogram()
}
serverLog := log.NewEntry(log.StandardLogger())
streamInterceptors := []grpc.StreamServerInterceptor{grpc_logrus.StreamServerInterceptor(serverLog), grpc_prometheus.StreamServerInterceptor, grpc_util.PanicLoggerStreamServerInterceptor(serverLog)}
@@ -73,10 +79,15 @@ func NewServer(metricsServer *metrics.MetricsServer, cache *reposervercache.Cach
// CreateGRPC creates new configured grpc server
func (a *ArgoCDRepoServer) CreateGRPC() *grpc.Server {
server := grpc.NewServer(a.opts...)
versionpkg.RegisterVersionServiceServer(server, &version.Server{})
versionpkg.RegisterVersionServiceServer(server, version.NewServer(nil, func() (bool, error) {
return true, nil
}))
manifestService := repository.NewService(a.metricsServer, a.cache, a.initConstants)
apiclient.RegisterRepoServerServiceServer(server, manifestService)
healthService := health.NewServer()
grpc_health_v1.RegisterHealthServer(server, healthService)
// Register reflection service on gRPC server.
reflection.Register(server)

View File

@@ -16,7 +16,7 @@ actions["retry"] = {["disabled"] = fullyPromoted or not(obj.status.abort)}
actions["promote-full"] = {["disabled"] = true}
if obj.status ~= nil and not(fullyPromoted) then
generation = tonumber(obj.status.observedGeneration)
if generation == nil then
if generation == nil or generation > obj.metadata.generation then
-- rollouts v0.9 - full promotion only supported for canary
actions["promote-full"] = {["disabled"] = obj.spec.strategy.blueGreen ~= nil}
else

View File

@@ -1,6 +1,6 @@
if obj.status ~= nil then
generation = tonumber(obj.status.observedGeneration)
if generation == nil then
if generation == nil or generation > obj.metadata.generation then
-- rollouts v0.9 and below
obj.status.abort = nil
if obj.spec.strategy.canary.steps ~= nil then

View File

@@ -51,15 +51,23 @@ function checkPaused(obj)
return nil
end
hs = {}
if obj.status == nil or obj.status.observedGeneration == nil then
hs.status = "Progressing"
hs.message = "Waiting for rollout spec update to be observed"
return hs
-- isGenerationObserved determines if the rollout spec has been observed by the controller. This
-- only applies to v0.10 rollout which uses a numeric status.observedGeneration. For v0.9 rollouts
-- and below this function always returns true.
function isGenerationObserved(obj)
if obj.status == nil then
return false
end
observedGeneration = tonumber(obj.status.observedGeneration)
if observedGeneration == nil or observedGeneration > obj.metadata.generation then
-- if we get here, the rollout is a v0.9 rollout
return true
end
return observedGeneration == obj.metadata.generation
end
generation = tonumber(obj.status.observedGeneration)
if generation ~= nil and generation ~= obj.metadata.generation then
hs = {}
if not isGenerationObserved(obj) then
hs.status = "Progressing"
hs.message = "Waiting for rollout spec update to be observed"
return hs

View File

@@ -11,6 +11,10 @@ tests:
status: Healthy
message: ""
inputPath: testdata/healthy_legacy_v0.9_observedGeneration.yaml
- healthStatus:
status: Healthy
message: ""
inputPath: testdata/healthy_legacy_v0.9_observedGeneration_numeric.yaml
- healthStatus:
status: Degraded
message: "The Rollout \"basic\" is invalid: spec.strategy.strategy: Required value: Rollout has missing field '.spec.strategy.canary or .spec.strategy.blueGreen'"

View File

@@ -0,0 +1,60 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
creationTimestamp: "2020-11-13T00:44:55Z"
generation: 1
name: basic
namespace: argocd-e2e
resourceVersion: "182108"
selfLink: /apis/argoproj.io/v1alpha1/namespaces/argocd-e2e/rollouts/basic
uid: 34e4bbfc-222c-4968-bd60-2b30ae81110d
spec:
replicas: 1
selector:
matchLabels:
app: basic
strategy:
canary:
steps:
- setWeight: 50
- pause: {}
template:
metadata:
creationTimestamp: null
labels:
app: basic
spec:
containers:
- image: nginx:1.19-alpine
name: basic
resources:
requests:
cpu: 1m
memory: 16Mi
status:
HPAReplicas: 1
availableReplicas: 1
blueGreen: {}
canary: {}
conditions:
- lastTransitionTime: "2020-11-13T00:48:20Z"
lastUpdateTime: "2020-11-13T00:48:22Z"
message: ReplicaSet "basic-754cb84d5" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
- lastTransitionTime: "2020-11-13T00:48:22Z"
lastUpdateTime: "2020-11-13T00:48:22Z"
message: Rollout has minimum availability
reason: AvailableReason
status: "True"
type: Available
currentPodHash: 754cb84d5
currentStepHash: 757f5f97b
currentStepIndex: 2
observedGeneration: "8575574967" ## <---- uses legacy observedGeneration hash which are numbers
readyReplicas: 1
replicas: 1
selector: app=basic
stableRS: 754cb84d5
updatedReplicas: 1

View File

@@ -5,7 +5,7 @@ import (
"testing"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/v4"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@@ -17,6 +17,7 @@ import (
"github.com/argoproj/argo-cd/pkg/apiclient/account"
sessionpkg "github.com/argoproj/argo-cd/pkg/apiclient/session"
"github.com/argoproj/argo-cd/server/session"
"github.com/argoproj/argo-cd/test"
"github.com/argoproj/argo-cd/util/errors"
"github.com/argoproj/argo-cd/util/password"
"github.com/argoproj/argo-cd/util/rbac"
@@ -63,7 +64,7 @@ func newTestAccountServerExt(ctx context.Context, enforceFn rbac.ClaimsEnforcerF
}
kubeclientset := fake.NewSimpleClientset(cm, secret)
settingsMgr := settings.NewSettingsManager(ctx, kubeclientset, testNamespace)
sessionMgr := sessionutil.NewSessionManager(settingsMgr, "", sessionutil.NewInMemoryUserStateStorage())
sessionMgr := sessionutil.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", sessionutil.NewInMemoryUserStateStorage())
enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil)
enforcer.SetClaimsEnforcerFunc(enforceFn)
@@ -89,7 +90,7 @@ func ssoAdminContext(ctx context.Context, iat time.Time) context.Context {
return context.WithValue(ctx, "claims", &jwt.StandardClaims{
Subject: "admin",
Issuer: "https://myargocdhost.com/api/dex",
IssuedAt: iat.Unix(),
IssuedAt: jwt.At(iat),
})
}

View File

@@ -576,11 +576,12 @@ func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchReque
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Patch type '%s' is not supported", q.PatchType))
}
err = json.Unmarshal(patchApp, &app)
newApp := &v1alpha1.Application{}
err = json.Unmarshal(patchApp, newApp)
if err != nil {
return nil, err
}
return s.validateAndUpdateApp(ctx, app, false, true)
return s.validateAndUpdateApp(ctx, newApp, false, true)
}
// Delete removes an application and all associated resources
@@ -747,12 +748,12 @@ func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Applica
return err
}
if err := argo.ValidateDestination(ctx, &app.Spec.Destination, s.db); err != nil {
return status.Errorf(codes.InvalidArgument, "application destination spec is invalid: %s", err.Error())
}
var conditions []appv1.ApplicationCondition
if validate {
if err := argo.ValidateDestination(ctx, &app.Spec.Destination, s.db); err != nil {
return status.Errorf(codes.InvalidArgument, "application destination spec is invalid: %s", err.Error())
}
conditions, err = argo.ValidateRepo(ctx, app, s.repoClientset, s.db, kustomizeOptions, plugins, s.kubectl)
if err != nil {
return err
@@ -898,6 +899,10 @@ func (s *Server) PatchResource(ctx context.Context, q *application.ApplicationRe
manifest, err := s.kubectl.PatchResource(ctx, config, res.GroupKindVersion(), res.Name, res.Namespace, types.PatchType(q.PatchType), []byte(q.Patch))
if err != nil {
// don't expose real error for secrets since it might contain secret data
if res.Kind == kube.SecretKind && res.Group == "" {
return nil, fmt.Errorf("failed to patch Secret %s/%s", res.Namespace, res.Name)
}
return nil, err
}
manifest, err = replaceSecretValues(manifest)
@@ -1292,7 +1297,7 @@ func (s *Server) resolveRevision(ctx context.Context, app *appv1.Application, sy
if helm.IsVersion(ambiguousRevision) {
return ambiguousRevision, ambiguousRevision, nil
}
client := helm.NewClient(repo.Repo, repo.GetHelmCreds(), repo.EnableOCI)
client := helm.NewClient(repo.Repo, repo.GetHelmCreds(), repo.EnableOCI || app.Spec.Source.IsHelmOci())
index, err := client.GetIndex()
if err != nil {
return "", "", err

View File

@@ -9,7 +9,7 @@ import (
synccommon "github.com/argoproj/gitops-engine/pkg/sync/common"
"github.com/argoproj/gitops-engine/pkg/utils/kube/kubetest"
"github.com/argoproj/pkg/sync"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/v4"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@@ -215,6 +215,26 @@ spec:
name: fake-cluster
`
const fakeAppWithAnnotations = `
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: test-app
namespace: default
annotations:
test.annotation: test
spec:
source:
path: some/path
repoURL: https://github.com/argoproj/argocd-example-apps.git
targetRevision: HEAD
ksonnet:
environment: default
destination:
namespace: ` + test.FakeDestNamespace + `
server: https://cluster-api.com
`
func newTestAppWithDestName(opts ...func(app *appsv1.Application)) *appsv1.Application {
return createTestApp(fakeAppWithDestName, opts...)
}
@@ -223,6 +243,10 @@ func newTestApp(opts ...func(app *appsv1.Application)) *appsv1.Application {
return createTestApp(fakeApp, opts...)
}
func newTestAppWithAnnotations(opts ...func(app *appsv1.Application)) *appsv1.Application {
return createTestApp(fakeAppWithAnnotations, opts...)
}
func createTestApp(testApp string, opts ...func(app *appsv1.Application)) *appsv1.Application {
var app appsv1.Application
err := yaml.Unmarshal([]byte(testApp), &app)
@@ -499,7 +523,7 @@ p, admin, applications, update, my-proj/test-app, allow
}
func TestAppJsonPatch(t *testing.T) {
testApp := newTestApp()
testApp := newTestAppWithAnnotations()
ctx := context.Background()
// nolint:staticcheck
ctx = context.WithValue(ctx, "claims", &jwt.StandardClaims{Subject: "admin"})
@@ -517,6 +541,10 @@ func TestAppJsonPatch(t *testing.T) {
app, err = appServer.Patch(ctx, &application.ApplicationPatchRequest{Name: &testApp.Name, Patch: `[{"op": "replace", "path": "/spec/source/path", "value": "foo"}]`})
assert.NoError(t, err)
assert.Equal(t, "foo", app.Spec.Source.Path)
app, err = appServer.Patch(ctx, &application.ApplicationPatchRequest{Name: &testApp.Name, Patch: `[{"op": "remove", "path": "/metadata/annotations/test.annotation"}]`})
assert.NoError(t, err)
assert.NotContains(t, app.Annotations, "test.annotation")
}
func TestAppMergePatch(t *testing.T) {

97
server/logout/logout.go Normal file
View File

@@ -0,0 +1,97 @@
package logout
import (
"fmt"
"net/http"
"regexp"
"strings"
"github.com/dgrijalva/jwt-go/v4"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
"github.com/argoproj/argo-cd/util/session"
"github.com/argoproj/argo-cd/util/settings"
jwtutil "github.com/argoproj/argo-cd/util/jwt"
)
//NewHandler creates handler serving to do api/logout endpoint
func NewHandler(appClientset versioned.Interface, settingsMrg *settings.SettingsManager, sessionMgr *session.SessionManager, rootPath, namespace string) *Handler {
return &Handler{
appClientset: appClientset,
namespace: namespace,
settingsMgr: settingsMrg,
rootPath: rootPath,
verifyToken: sessionMgr.VerifyToken,
}
}
type Handler struct {
namespace string
appClientset versioned.Interface
settingsMgr *settings.SettingsManager
rootPath string
verifyToken func(tokenString string) (jwt.Claims, error)
}
var (
tokenPattern = regexp.MustCompile(`{{token}}`)
logoutRedirectURLPattern = regexp.MustCompile(`{{logoutRedirectURL}}`)
)
func constructLogoutURL(logoutURL, token, logoutRedirectURL string) string {
constructedLogoutURL := tokenPattern.ReplaceAllString(logoutURL, token)
return logoutRedirectURLPattern.ReplaceAllString(constructedLogoutURL, logoutRedirectURL)
}
// ServeHTTP is the logout handler for ArgoCD and constructs OIDC logout URL and redirects to it for OIDC issued sessions,
// and redirects user to '/login' for argocd issued sessions
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var tokenString string
var oidcConfig *settings.OIDCConfig
argoCDSettings, err := h.settingsMgr.GetSettings()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
http.Error(w, "Failed to retrieve argoCD settings: "+fmt.Sprintf("%s", err), http.StatusInternalServerError)
return
}
logoutRedirectURL := strings.TrimRight(strings.TrimLeft(argoCDSettings.URL, "/"), "/") + strings.TrimRight(strings.TrimLeft(h.rootPath, "/"), "/")
argocdCookie, err := r.Cookie(common.AuthCookieName)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
http.Error(w, "Failed to retrieve ArgoCD auth token: "+fmt.Sprintf("%s", err), http.StatusBadRequest)
return
}
tokenString = argocdCookie.Value
argocdCookie.Value = ""
argocdCookie.Path = fmt.Sprintf("/%s", strings.TrimRight(strings.TrimLeft(h.rootPath, "/"), "/"))
w.Header().Set("Set-Cookie", argocdCookie.String())
claims, err := h.verifyToken(tokenString)
if err != nil {
http.Redirect(w, r, logoutRedirectURL, http.StatusSeeOther)
return
}
mapClaims, err := jwtutil.MapClaims(claims)
if err != nil {
http.Redirect(w, r, logoutRedirectURL, http.StatusSeeOther)
return
}
issuer := jwtutil.StringField(mapClaims, "iss")
if argoCDSettings.OIDCConfig() == nil || argoCDSettings.OIDCConfig().LogoutURL == "" || issuer == session.SessionManagerClaimsIssuer {
http.Redirect(w, r, logoutRedirectURL, http.StatusSeeOther)
} else {
oidcConfig = argoCDSettings.OIDCConfig()
logoutURL := constructLogoutURL(oidcConfig.LogoutURL, tokenString, logoutRedirectURL)
http.Redirect(w, r, logoutURL, http.StatusSeeOther)
}
}

View File

@@ -0,0 +1,290 @@
package logout
import (
"context"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"regexp"
"testing"
"github.com/argoproj/argo-cd/common"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake"
"github.com/argoproj/argo-cd/test"
"github.com/argoproj/argo-cd/util/session"
"github.com/argoproj/argo-cd/util/settings"
"github.com/dgrijalva/jwt-go/v4"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
var (
validJWTPattern = regexp.MustCompile(`[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+`)
baseURL = "http://localhost:4000"
baseLogoutURL = "http://localhost:4000/logout"
baseLogoutURLwithToken = "http://localhost:4000/logout?id_token_hint={{token}}"
baseLogoutURLwithRedirectURL = "http://localhost:4000/logout?post_logout_redirect_uri={{logoutRedirectURL}}"
baseLogoutURLwithTokenAndRedirectURL = "http://localhost:4000/logout?id_token_hint={{token}}&post_logout_redirect_uri={{logoutRedirectURL}}"
invalidToken = "sample-token"
oidcToken = "eyJraWQiOiJYQi1MM3ZFdHhYWXJLcmRSQnVEV0NwdnZsSnk3SEJVb2d5N253M1U1Z1ZZIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIwMHVqNnM1NDVyNU5peVNLcjVkNSIsIm5hbWUiOiJqZCByIiwiZW1haWwiOiJqYWlkZWVwMTdydWx6QGdtYWlsLmNvbSIsInZlciI6MSwiaXNzIjoiaHR0cHM6Ly9kZXYtNTY5NTA5OC5va3RhLmNvbSIsImF1ZCI6IjBvYWowM2FmSEtqN3laWXJwNWQ1IiwiaWF0IjoxNjA1NTcyMzU5LCJleHAiOjE2MDU1NzU5NTksImp0aSI6IklELl9ORDJxVG5iREFtc3hIZUt2U2ZHeVBqTXRicXFEQXdkdlRQTDZCTnpfR3ciLCJhbXIiOlsicHdkIl0sImlkcCI6IjAwb2lnaGZmdkpRTDYzWjhoNWQ1IiwicHJlZmVycmVkX3VzZXJuYW1lIjoiamFpZGVlcDE3cnVsekBnbWFpbC5jb20iLCJhdXRoX3RpbWUiOjE2MDU1NzIzNTcsImF0X2hhc2giOiJqZVEwRml2ak9nNGI2TUpXRDIxOWxnIn0.GHkqwXgW-lrAhJdypW7SVjW0YdNLFQiRL8iwgT6DHJxP9Nb0OtkH2NKcBYAA5N6bTPLRQUHgYwWcgm5zSXmvqa7ciIgPF3tiQI8UmJA9VFRRDR-x9ExX15nskCbXfiQ67MriLslUrQUyzSCfUrSjXKwnDxbKGQncrtmRsh5asfCzJFb9excn311W9HKbT3KA0Ot7eOMnVS6V7SGfXxnKs6szcXIEMa_FhB4zDAVLr-dnxvSG_uuWcHrAkLTUVhHbdQQXF7hXIEfyr5lkMJN-drjdz-bn40GaYulEmUvO1bjcL9toCVQ3Ismypyr0b8phj4w3uRsLDZQxTxK7jAXlyQ"
nonOidcToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MDU1NzQyMTIsImlzcyI6ImFyZ29jZCIsIm5iZiI6MTYwNTU3NDIxMiwic3ViIjoiYWRtaW4ifQ.zDJ4piwWnwsHON-oPusHMXWINlnrRDTQykYogT7afeE"
expectedNonOIDCLogoutURL = "http://localhost:4000"
expectedOIDCLogoutURL = "https://dev-5695098.okta.com/oauth2/v1/logout?id_token_hint=" + oidcToken + "&post_logout_redirect_uri=" + baseURL
)
func TestConstructLogoutURL(t *testing.T) {
tests := []struct {
name string
logoutURL string
token string
logoutRedirectURL string
expectedLogoutURL string
}{
{
name: "Case: No additional parameters passed to logout URL",
logoutURL: baseLogoutURL,
token: oidcToken,
logoutRedirectURL: baseURL,
expectedLogoutURL: baseLogoutURL,
},
{
name: "Case: ID token passed to logout URL",
logoutURL: baseLogoutURLwithToken,
token: oidcToken,
logoutRedirectURL: baseURL,
expectedLogoutURL: "http://localhost:4000/logout?id_token_hint=" + oidcToken,
},
{
name: "Case: Redirect required",
logoutURL: baseLogoutURLwithRedirectURL,
token: oidcToken,
logoutRedirectURL: baseURL,
expectedLogoutURL: "http://localhost:4000/logout?post_logout_redirect_uri=" + baseURL,
},
{
name: "Case: ID token and redirect URL passed to logout URL",
logoutURL: baseLogoutURLwithTokenAndRedirectURL,
token: oidcToken,
logoutRedirectURL: baseURL,
expectedLogoutURL: "http://localhost:4000/logout?id_token_hint=" + oidcToken + "&post_logout_redirect_uri=" + baseURL,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
constructedLogoutURL := constructLogoutURL(tt.logoutURL, tt.token, tt.logoutRedirectURL)
assert.Equal(t, constructedLogoutURL, tt.expectedLogoutURL)
})
}
}
func TestHandlerConstructLogoutURL(t *testing.T) {
kubeClientWithOIDCConfig := fake.NewSimpleClientset(
&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: common.ArgoCDConfigMapName,
Namespace: "default",
Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
},
},
Data: map[string]string{
"oidc.config": "name: Okta \n" +
"issuer: https://dev-5695098.okta.com \n" +
"requestedScopes: [\"openid\", \"profile\", \"email\", \"groups\"] \n" +
"requestedIDTokenClaims: {\"groups\": {\"essential\": true}} \n" +
"logoutURL: https://dev-5695098.okta.com/oauth2/v1/logout?id_token_hint={{token}}&post_logout_redirect_uri={{logoutRedirectURL}}",
"url": "http://localhost:4000",
},
},
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: common.ArgoCDSecretName,
Namespace: "default",
Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
},
},
Data: map[string][]byte{
"admin.password": nil,
"server.secretkey": nil,
},
},
)
kubeClientWithOIDCConfigButNoLogoutURL := fake.NewSimpleClientset(
&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: common.ArgoCDConfigMapName,
Namespace: "default",
Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
},
},
Data: map[string]string{
"oidc.config": "name: Okta \n" +
"issuer: https://dev-5695098.okta.com \n" +
"requestedScopes: [\"openid\", \"profile\", \"email\", \"groups\"] \n" +
"requestedIDTokenClaims: {\"groups\": {\"essential\": true}} \n",
"url": "http://localhost:4000",
},
},
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: common.ArgoCDSecretName,
Namespace: "default",
Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
},
},
Data: map[string][]byte{
"admin.password": nil,
"server.secretkey": nil,
},
},
)
kubeClientWithoutOIDCConfig := fake.NewSimpleClientset(
&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: common.ArgoCDConfigMapName,
Namespace: "default",
Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
},
},
Data: map[string]string{
"url": "http://localhost:4000",
},
},
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: common.ArgoCDSecretName,
Namespace: "default",
Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
},
},
Data: map[string][]byte{
"admin.password": nil,
"server.secretkey": nil,
},
},
)
settingsManagerWithOIDCConfig := settings.NewSettingsManager(context.Background(), kubeClientWithOIDCConfig, "default")
settingsManagerWithoutOIDCConfig := settings.NewSettingsManager(context.Background(), kubeClientWithoutOIDCConfig, "default")
settingsManagerWithOIDCConfigButNoLogoutURL := settings.NewSettingsManager(context.Background(), kubeClientWithOIDCConfigButNoLogoutURL, "default")
sessionManager := session.NewSessionManager(settingsManagerWithOIDCConfig, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
oidcHandler := NewHandler(appclientset.NewSimpleClientset(), settingsManagerWithOIDCConfig, sessionManager, "", "default")
oidcHandler.verifyToken = func(tokenString string) (jwt.Claims, error) {
if !validJWTPattern.MatchString(tokenString) {
return nil, errors.New("invalid jwt")
}
return &jwt.StandardClaims{Issuer: "okta"}, nil
}
nonoidcHandler := NewHandler(appclientset.NewSimpleClientset(), settingsManagerWithoutOIDCConfig, sessionManager, "", "default")
nonoidcHandler.verifyToken = func(tokenString string) (jwt.Claims, error) {
if !validJWTPattern.MatchString(tokenString) {
return nil, errors.New("invalid jwt")
}
return &jwt.StandardClaims{Issuer: session.SessionManagerClaimsIssuer}, nil
}
oidcHandlerWithoutLogoutURL := NewHandler(appclientset.NewSimpleClientset(), settingsManagerWithOIDCConfigButNoLogoutURL, sessionManager, "", "default")
oidcHandlerWithoutLogoutURL.verifyToken = func(tokenString string) (jwt.Claims, error) {
if !validJWTPattern.MatchString(tokenString) {
return nil, errors.New("invalid jwt")
}
return &jwt.StandardClaims{Issuer: "okta"}, nil
}
oidcTokenHeader := make(map[string][]string)
oidcTokenHeader["Cookie"] = []string{"argocd.token=" + oidcToken}
nonOidcTokenHeader := make(map[string][]string)
nonOidcTokenHeader["Cookie"] = []string{"argocd.token=" + nonOidcToken}
invalidHeader := make(map[string][]string)
invalidHeader["Cookie"] = []string{"argocd.token=" + invalidToken}
oidcRequest, err := http.NewRequest("GET", "http://localhost:4000/api/logout", nil)
assert.NoError(t, err)
oidcRequest.Header = oidcTokenHeader
nonoidcRequest, err := http.NewRequest("GET", "http://localhost:4000/api/logout", nil)
assert.NoError(t, err)
nonoidcRequest.Header = nonOidcTokenHeader
assert.NoError(t, err)
requestWithInvalidToken, err := http.NewRequest("GET", "http://localhost:4000/api/logout", nil)
assert.NoError(t, err)
requestWithInvalidToken.Header = invalidHeader
invalidRequest, err := http.NewRequest("GET", "http://localhost:4000/api/logout", nil)
assert.NoError(t, err)
tests := []struct {
name string
kubeClient *fake.Clientset
handler http.Handler
request *http.Request
responseRecorder *httptest.ResponseRecorder
expectedLogoutURL string
wantErr bool
}{
{
name: "Case: OIDC logout request with valid token",
handler: oidcHandler,
request: oidcRequest,
responseRecorder: httptest.NewRecorder(),
expectedLogoutURL: expectedOIDCLogoutURL,
wantErr: false,
},
{
name: "Case: non-OIDC logout request with valid token",
handler: nonoidcHandler,
request: nonoidcRequest,
responseRecorder: httptest.NewRecorder(),
expectedLogoutURL: expectedNonOIDCLogoutURL,
wantErr: false,
},
{
name: "Case: Logout request with invalid token",
handler: nonoidcHandler,
request: requestWithInvalidToken,
responseRecorder: httptest.NewRecorder(),
expectedLogoutURL: expectedNonOIDCLogoutURL,
wantErr: false,
},
{
name: "Case: Logout request with missing token",
handler: oidcHandler,
request: invalidRequest,
responseRecorder: httptest.NewRecorder(),
expectedLogoutURL: expectedNonOIDCLogoutURL,
wantErr: true,
},
{
name: "Case:OIDC Logout request with missing logout URL configuration in config map",
handler: oidcHandlerWithoutLogoutURL,
request: oidcRequest,
responseRecorder: httptest.NewRecorder(),
expectedLogoutURL: expectedNonOIDCLogoutURL,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.handler.ServeHTTP(tt.responseRecorder, tt.request)
if status := tt.responseRecorder.Code; status != http.StatusSeeOther {
if !tt.wantErr {
t.Errorf(tt.responseRecorder.Body.String())
t.Errorf("handler returned wrong status code: " + fmt.Sprintf("%d", tt.responseRecorder.Code))
}
} else {
if tt.wantErr {
t.Errorf("expected error but did not get one")
} else {
assert.Equal(t, tt.expectedLogoutURL, tt.responseRecorder.Result().Header["Location"][0])
}
}
})
}
}

View File

@@ -7,7 +7,7 @@ import (
"strings"
"github.com/argoproj/pkg/sync"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/v4"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
@@ -108,16 +108,21 @@ func (s *Server) CreateToken(ctx context.Context, q *project.ProjectTokenCreateR
return nil, status.Error(codes.InvalidArgument, err.Error())
}
parser := &jwt.Parser{
SkipClaimsValidation: true,
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation(), jwt.WithoutAudienceValidation()),
}
claims := jwt.StandardClaims{}
_, _, err = parser.ParseUnverified(jwtToken, &claims)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
issuedAt := claims.IssuedAt
expiresAt := claims.ExpiresAt
id = claims.Id
var issuedAt, expiresAt int64
if claims.IssuedAt != nil {
issuedAt = claims.IssuedAt.Unix()
}
if claims.ExpiresAt != nil {
expiresAt = claims.ExpiresAt.Unix()
}
id = claims.ID
items := append(prj.Status.JWTTokensByRole[q.Role].Items, v1alpha1.JWTToken{IssuedAt: issuedAt, ExpiresAt: expiresAt, ID: id})
if _, found := prj.Status.JWTTokensByRole[q.Role]; found {

View File

@@ -7,6 +7,7 @@ import (
"testing"
"github.com/argoproj/pkg/sync"
"github.com/dgrijalva/jwt-go/v4"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
@@ -17,14 +18,13 @@ import (
"k8s.io/client-go/kubernetes/fake"
k8scache "k8s.io/client-go/tools/cache"
"github.com/dgrijalva/jwt-go"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apiclient/project"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
apps "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake"
informer "github.com/argoproj/argo-cd/pkg/client/informers/externalversions"
"github.com/argoproj/argo-cd/server/rbacpolicy"
"github.com/argoproj/argo-cd/test"
"github.com/argoproj/argo-cd/util/assets"
jwtutil "github.com/argoproj/argo-cd/util/jwt"
"github.com/argoproj/argo-cd/util/rbac"
@@ -82,7 +82,7 @@ func TestProjectServer(t *testing.T) {
}
t.Run("TestNormalizeProj", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projectWithRole := existingProj.DeepCopy()
roleName := "roleName"
role1 := v1alpha1.ProjectRole{Name: roleName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}}
@@ -319,7 +319,7 @@ func TestProjectServer(t *testing.T) {
id := "testId"
t.Run("TestCreateTokenDenied", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projectWithRole := existingProj.DeepCopy()
projectWithRole.Spec.Roles = []v1alpha1.ProjectRole{{Name: tokenName}}
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projectWithRole), enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr)
@@ -328,7 +328,7 @@ func TestProjectServer(t *testing.T) {
})
t.Run("TestCreateTokenSuccessfullyUsingGroup", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projectWithRole := existingProj.DeepCopy()
projectWithRole.Spec.Roles = []v1alpha1.ProjectRole{{Name: tokenName, Groups: []string{"my-group"}}}
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projectWithRole), enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr)
@@ -339,11 +339,13 @@ func TestProjectServer(t *testing.T) {
_ = enforcer.SetBuiltinPolicy(`p, role:admin, projects, update, *, allow`)
t.Run("TestCreateTokenSuccessfully", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
projectWithRole := existingProj.DeepCopy()
projectWithRole.Spec.Roles = []v1alpha1.ProjectRole{{Name: tokenName}}
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projectWithRole), enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr)
tokenResponse, err := projectServer.CreateToken(context.Background(), &project.ProjectTokenCreateRequest{Project: projectWithRole.Name, Role: tokenName, ExpiresIn: 1})
clientset := apps.NewSimpleClientset(projectWithRole)
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjListerFromInterface(clientset.ArgoprojV1alpha1().AppProjects("default")), "", session.NewInMemoryUserStateStorage())
projectServer := NewServer("default", fake.NewSimpleClientset(), clientset, enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr)
tokenResponse, err := projectServer.CreateToken(context.Background(), &project.ProjectTokenCreateRequest{Project: projectWithRole.Name, Role: tokenName, ExpiresIn: 100})
assert.NoError(t, err)
claims, err := sessionMgr.Parse(tokenResponse.Token)
assert.NoError(t, err)
@@ -357,10 +359,12 @@ func TestProjectServer(t *testing.T) {
})
t.Run("TestCreateTokenWithIDSuccessfully", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
projectWithRole := existingProj.DeepCopy()
projectWithRole.Spec.Roles = []v1alpha1.ProjectRole{{Name: tokenName}}
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projectWithRole), enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr)
clientset := apps.NewSimpleClientset(projectWithRole)
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjListerFromInterface(clientset.ArgoprojV1alpha1().AppProjects("default")), "", session.NewInMemoryUserStateStorage())
projectServer := NewServer("default", fake.NewSimpleClientset(), clientset, enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr)
tokenResponse, err := projectServer.CreateToken(context.Background(), &project.ProjectTokenCreateRequest{Project: projectWithRole.Name, Role: tokenName, ExpiresIn: 1, Id: id})
assert.NoError(t, err)
claims, err := sessionMgr.Parse(tokenResponse.Token)
@@ -375,10 +379,12 @@ func TestProjectServer(t *testing.T) {
})
t.Run("TestCreateTokenWithSameIdDeny", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
projectWithRole := existingProj.DeepCopy()
projectWithRole.Spec.Roles = []v1alpha1.ProjectRole{{Name: tokenName}}
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projectWithRole), enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr)
clientset := apps.NewSimpleClientset(projectWithRole)
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjListerFromInterface(clientset.ArgoprojV1alpha1().AppProjects("default")), "", session.NewInMemoryUserStateStorage())
projectServer := NewServer("default", fake.NewSimpleClientset(), clientset, enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr)
tokenResponse, err := projectServer.CreateToken(context.Background(), &project.ProjectTokenCreateRequest{Project: projectWithRole.Name, Role: tokenName, ExpiresIn: 1, Id: id})
assert.NoError(t, err)
@@ -400,7 +406,7 @@ func TestProjectServer(t *testing.T) {
_ = enforcer.SetBuiltinPolicy(`p, *, *, *, *, deny`)
t.Run("TestDeleteTokenDenied", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projWithToken := existingProj.DeepCopy()
issuedAt := int64(1)
secondIssuedAt := issuedAt + 1
@@ -413,7 +419,7 @@ func TestProjectServer(t *testing.T) {
})
t.Run("TestDeleteTokenSuccessfullyWithGroup", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projWithToken := existingProj.DeepCopy()
issuedAt := int64(1)
secondIssuedAt := issuedAt + 1
@@ -429,7 +435,7 @@ func TestProjectServer(t *testing.T) {
p, role:admin, projects, update, *, allow`)
t.Run("TestDeleteTokenSuccessfully", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projWithToken := existingProj.DeepCopy()
issuedAt := int64(1)
secondIssuedAt := issuedAt + 1
@@ -450,7 +456,7 @@ p, role:admin, projects, update, *, allow`)
p, role:admin, projects, update, *, allow`)
t.Run("TestDeleteTokenByIdSuccessfully", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projWithToken := existingProj.DeepCopy()
issuedAt := int64(1)
secondIssuedAt := issuedAt + 1
@@ -473,7 +479,7 @@ p, role:admin, projects, update, *, allow`)
enforcer = newEnforcer(kubeclientset)
t.Run("TestCreateTwoTokensInRoleSuccess", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projWithToken := existingProj.DeepCopy()
tokenName := "testToken"
token := v1alpha1.ProjectRole{Name: tokenName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}}
@@ -638,7 +644,7 @@ p, role:admin, projects, update, *, allow`)
})
t.Run("TestSyncWindowsActive", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projectWithSyncWindows := existingProj.DeepCopy()
projectWithSyncWindows.Spec.SyncWindows = v1alpha1.SyncWindows{}
win := &v1alpha1.SyncWindow{Kind: "allow", Schedule: "* * * * *", Duration: "1h"}
@@ -651,7 +657,7 @@ p, role:admin, projects, update, *, allow`)
})
t.Run("TestGetSyncWindowsStateCannotGetProjectDetails", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projectWithSyncWindows := existingProj.DeepCopy()
projectWithSyncWindows.Spec.SyncWindows = v1alpha1.SyncWindows{}
win := &v1alpha1.SyncWindow{Kind: "allow", Schedule: "* * * * *", Duration: "1h"}
@@ -670,7 +676,7 @@ p, role:admin, projects, update, *, allow`)
// nolint:staticcheck
ctx := context.WithValue(context.Background(), "claims", &jwt.MapClaims{"groups": []string{"my-group"}})
sessionMgr := session.NewSessionManager(settingsMgr, "", session.NewInMemoryUserStateStorage())
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", session.NewInMemoryUserStateStorage())
projectWithSyncWindows := existingProj.DeepCopy()
win := &v1alpha1.SyncWindow{Kind: "allow", Schedule: "* * * * *", Duration: "1h"}
projectWithSyncWindows.Spec.SyncWindows = append(projectWithSyncWindows.Spec.SyncWindows, win)

View File

@@ -3,7 +3,7 @@ package rbacpolicy
import (
"strings"
jwt "github.com/dgrijalva/jwt-go"
jwt "github.com/dgrijalva/jwt-go/v4"
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
@@ -82,7 +82,16 @@ func (p *RBACPolicyEnforcer) GetScopes() []string {
}
func IsProjectSubject(subject string) bool {
return strings.HasPrefix(subject, "proj:")
_, _, ok := GetProjectRoleFromSubject(subject)
return ok
}
func GetProjectRoleFromSubject(subject string) (string, string, bool) {
parts := strings.Split(subject, ":")
if len(parts) == 3 && parts[0] == "proj" {
return parts[1], parts[2], true
}
return "", "", false
}
// EnforceClaims is an RBAC claims enforcer specific to the Argo CD API server
@@ -92,14 +101,14 @@ func (p *RBACPolicyEnforcer) EnforceClaims(claims jwt.Claims, rvals ...interface
return false
}
subject := jwtutil.GetField(mapClaims, "sub")
subject := jwtutil.StringField(mapClaims, "sub")
// Check if the request is for an application resource. We have special enforcement which takes
// into consideration the project's token and group bindings
var runtimePolicy string
proj := p.getProjectFromRequest(rvals...)
if proj != nil {
if IsProjectSubject(subject) {
return p.enforceProjectToken(subject, mapClaims, proj, rvals...)
return p.enforceProjectToken(subject, proj, rvals...)
}
runtimePolicy = proj.ProjectPoliciesString()
}
@@ -158,31 +167,17 @@ func (p *RBACPolicyEnforcer) getProjectFromRequest(rvals ...interface{}) *v1alph
}
// enforceProjectToken will check to see the valid token has not yet been revoked in the project
func (p *RBACPolicyEnforcer) enforceProjectToken(subject string, claims jwt.MapClaims, proj *v1alpha1.AppProject, rvals ...interface{}) bool {
func (p *RBACPolicyEnforcer) enforceProjectToken(subject string, proj *v1alpha1.AppProject, rvals ...interface{}) bool {
subjectSplit := strings.Split(subject, ":")
if len(subjectSplit) != 3 {
return false
}
projName, roleName := subjectSplit[1], subjectSplit[2]
projName, _ := subjectSplit[1], subjectSplit[2]
if projName != proj.Name {
// this should never happen (we generated a project token for a different project)
return false
}
var iat int64 = -1
jti, err := jwtutil.GetID(claims)
if err != nil || jti == "" {
iat, err = jwtutil.GetIssuedAt(claims)
if err != nil {
return false
}
}
_, _, err = proj.GetJWTToken(roleName, iat, jti)
if err != nil {
// if we get here the token is still valid, but has been revoked (no longer exists in the project)
return false
}
vals := append([]interface{}{subject}, rvals[1:]...)
return p.enf.EnforceRuntimePolicy(proj.ProjectPoliciesString(), vals...)

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"testing"
jwt "github.com/dgrijalva/jwt-go"
jwt "github.com/dgrijalva/jwt-go/v4"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
@@ -67,12 +67,6 @@ func TestEnforceAllPolicies(t *testing.T) {
claims = jwt.MapClaims{"sub": "cathy"}
assert.False(t, enf.Enforce(claims, "applications", "create", "my-proj/my-app"))
claims = jwt.MapClaims{"sub": "proj:my-proj:my-role"}
assert.False(t, enf.Enforce(claims, "applications", "create", "my-proj/my-app"))
claims = jwt.MapClaims{"sub": "proj:my-proj:other-role", "iat": 1234}
assert.False(t, enf.Enforce(claims, "applications", "create", "my-proj/my-app"))
claims = jwt.MapClaims{"groups": []string{"my-org:other-group"}}
assert.False(t, enf.Enforce(claims, "applications", "create", "my-proj/my-app"))
// AWS cognito returns its groups in cognito:groups
rbacEnf.SetScopes([]string{"cognito:groups"})

View File

@@ -65,6 +65,9 @@ func (s *Server) ListRepositoryCredentials(ctx context.Context, q *repocredspkg.
// CreateRepositoryCredentials creates a new credential set in the configuration
func (s *Server) CreateRepositoryCredentials(ctx context.Context, q *repocredspkg.RepoCredsCreateRequest) (*appsv1.RepoCreds, error) {
if q.Creds == nil {
return nil, status.Errorf(codes.InvalidArgument, "missing payload in request")
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionCreate, q.Creds.URL); err != nil {
return nil, err
}
@@ -96,6 +99,9 @@ func (s *Server) CreateRepositoryCredentials(ctx context.Context, q *repocredspk
// UpdateRepositoryCredentials updates a repository credential set
func (s *Server) UpdateRepositoryCredentials(ctx context.Context, q *repocredspkg.RepoCredsUpdateRequest) (*appsv1.RepoCreds, error) {
if q.Creds == nil {
return nil, status.Errorf(codes.InvalidArgument, "missing payload in request")
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionUpdate, q.Creds.URL); err != nil {
return nil, err
}

View File

@@ -205,6 +205,9 @@ func (s *Server) ListApps(ctx context.Context, q *repositorypkg.RepoAppsQuery) (
}
func (s *Server) GetAppDetails(ctx context.Context, q *repositorypkg.RepoAppDetailsQuery) (*apiclient.RepoAppDetailsResponse, error) {
if q.Source == nil {
return nil, status.Errorf(codes.InvalidArgument, "missing payload in request")
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionGet, q.Source.RepoURL); err != nil {
return nil, err
}
@@ -262,6 +265,9 @@ func (s *Server) Create(ctx context.Context, q *repositorypkg.RepoCreateRequest)
// CreateRepository creates a repository configuration
func (s *Server) CreateRepository(ctx context.Context, q *repositorypkg.RepoCreateRequest) (*appsv1.Repository, error) {
if q.Repo == nil {
return nil, status.Errorf(codes.InvalidArgument, "missing payload in request")
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionCreate, q.Repo.Repo); err != nil {
return nil, err
}
@@ -320,6 +326,9 @@ func (s *Server) Update(ctx context.Context, q *repositorypkg.RepoUpdateRequest)
// UpdateRepository updates a repository configuration
func (s *Server) UpdateRepository(ctx context.Context, q *repositorypkg.RepoUpdateRequest) (*appsv1.Repository, error) {
if q.Repo == nil {
return nil, status.Errorf(codes.InvalidArgument, "missing payload in request")
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionUpdate, q.Repo.Repo); err != nil {
return nil, err
}

View File

@@ -21,7 +21,7 @@ import (
"github.com/argoproj/pkg/jwt/zjwt"
"github.com/argoproj/pkg/sync"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/v4"
"github.com/go-redis/redis/v8"
"github.com/gorilla/handlers"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
@@ -73,6 +73,7 @@ import (
"github.com/argoproj/argo-cd/server/certificate"
"github.com/argoproj/argo-cd/server/cluster"
"github.com/argoproj/argo-cd/server/gpgkey"
"github.com/argoproj/argo-cd/server/logout"
"github.com/argoproj/argo-cd/server/metrics"
"github.com/argoproj/argo-cd/server/project"
"github.com/argoproj/argo-cd/server/rbacpolicy"
@@ -104,7 +105,6 @@ import (
const maxConcurrentLoginRequestsCountEnv = "ARGOCD_MAX_CONCURRENT_LOGIN_REQUESTS_COUNT"
const replicasCountEnv = "ARGOCD_API_SERVER_REPLICAS"
const enableGRPCTimeHistogramEnv = "ARGOCD_ENABLE_GRPC_TIME_HISTOGRAM"
// ErrNoSession indicates no auth token was supplied as part of a request
var ErrNoSession = status.Errorf(codes.Unauthenticated, "no session information")
@@ -138,7 +138,7 @@ func init() {
if replicasCount > 0 {
maxConcurrentLoginRequestsCount = maxConcurrentLoginRequestsCount / replicasCount
}
enableGRPCTimeHistogram = os.Getenv(enableGRPCTimeHistogramEnv) != "false"
enableGRPCTimeHistogram = os.Getenv(common.EnvEnableGRPCTimeHistogramEnv) == "true"
}
// ArgoCDServer is the API server for Argo CD
@@ -209,8 +209,6 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
err = initializeDefaultProject(opts)
errors.CheckError(err)
sessionMgr := util_session.NewSessionManager(settingsMgr, opts.DexServerAddr, opts.Cache)
factory := appinformer.NewFilteredSharedInformerFactory(opts.AppClientset, 0, opts.Namespace, func(options *metav1.ListOptions) {})
projInformer := factory.Argoproj().V1alpha1().AppProjects().Informer()
projLister := factory.Argoproj().V1alpha1().AppProjects().Lister().AppProjects(opts.Namespace)
@@ -218,6 +216,7 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
appInformer := factory.Argoproj().V1alpha1().Applications().Informer()
appLister := factory.Argoproj().V1alpha1().Applications().Lister().Applications(opts.Namespace)
sessionMgr := util_session.NewSessionManager(settingsMgr, projLister, opts.DexServerAddr, opts.Cache)
enf := rbac.NewEnforcer(opts.KubeClientset, opts.Namespace, common.ArgoCDRBACConfigMapName, nil)
enf.EnableEnforce(!opts.DisableAuth)
err = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
@@ -241,6 +240,22 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
}
}
const (
// catches corrupted informer state; see https://github.com/argoproj/argo-cd/issues/4960 for more information
notObjectErrMsg = "object does not implement the Object interfaces"
)
func (a *ArgoCDServer) healthCheck(r *http.Request) error {
if val, ok := r.URL.Query()["full"]; ok && len(val) > 0 && val[0] == "true" {
argoDB := db.NewDB(a.Namespace, a.settingsMgr, a.KubeClientset)
_, err := argoDB.ListClusters(r.Context())
if err != nil && strings.Contains(err.Error(), notObjectErrMsg) {
return err
}
}
return nil
}
// Run runs the API Server
// We use k8s.io/code-generator/cmd/go-to-protobuf to generate the .proto files from the API types.
// k8s.io/ go-to-protobuf uses protoc-gen-gogo, which comes from gogo/protobuf (a fork of
@@ -546,7 +561,16 @@ func (a *ArgoCDServer) newGRPCServer() *grpc.Server {
accountService := account.NewServer(a.sessionMgr, a.settingsMgr, a.enf)
certificateService := certificate.NewServer(a.RepoClientset, db, a.enf)
gpgkeyService := gpgkey.NewServer(a.RepoClientset, db, a.enf)
versionpkg.RegisterVersionServiceServer(grpcS, &version.Server{})
versionpkg.RegisterVersionServiceServer(grpcS, version.NewServer(a, func() (bool, error) {
if a.DisableAuth {
return true, nil
}
sett, err := a.settingsMgr.GetSettings()
if err != nil {
return false, err
}
return sett.AnonymousUserEnabled, err
}))
clusterpkg.RegisterClusterServiceServer(grpcS, clusterService)
applicationpkg.RegisterApplicationServiceServer(grpcS, applicationService)
repositorypkg.RegisterRepositoryServiceServer(grpcS, repoService)
@@ -596,9 +620,7 @@ func withRootPath(handler http.Handler, a *ArgoCDServer) http.Handler {
mux := http.NewServeMux()
mux.Handle("/"+root+"/", http.StripPrefix("/"+root, handler))
healthz.ServeHealthCheck(mux, func() error {
return nil
})
healthz.ServeHealthCheck(mux, a.healthCheck)
return mux
}
@@ -624,7 +646,8 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
Handler: &handlerSwitcher{
handler: mux,
urlToHandler: map[string]http.Handler{
"/api/badge": badge.NewHandler(a.AppClientset, a.settingsMgr, a.Namespace),
"/api/badge": badge.NewHandler(a.AppClientset, a.settingsMgr, a.Namespace),
common.LogoutEndpoint: logout.NewHandler(a.AppClientset, a.settingsMgr, a.sessionMgr, a.ArgoCDServerOpts.RootPath, a.Namespace),
},
contentTypeToHandler: map[string]http.Handler{
"application/grpc-web+proto": grpcWebHandler,
@@ -680,9 +703,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
// Swagger UI
swagger.ServeSwaggerUI(mux, assets.SwaggerJSON, "/swagger-ui", a.RootPath)
healthz.ServeHealthCheck(mux, func() error {
return nil
})
healthz.ServeHealthCheck(mux, a.healthCheck)
// Dex reverse proxy and client app and OAuth2 login/callback
a.registerDexHandlers(mux)
@@ -820,6 +841,7 @@ func (server *ArgoCDServer) newStaticAssetsHandler(dir string, baseHRef string)
if server.XFrameOptions != "" {
w.Header().Set("X-Frame-Options", server.XFrameOptions)
}
w.Header().Set("X-XSS-Protection", "1")
// serve index.html for non file requests to support HTML5 History API
if acceptHTML && !fileRequest && (r.Method == "GET" || r.Method == "HEAD") {

View File

@@ -7,7 +7,7 @@ import (
"testing"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/v4"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/metadata"
@@ -362,13 +362,6 @@ func TestRevokedToken(t *testing.T) {
claims := jwt.MapClaims{"sub": defaultSub, "iat": defaultIssuedAt}
assert.True(t, s.enf.Enforce(claims, "projects", "get", existingProj.ObjectMeta.Name))
assert.True(t, s.enf.Enforce(claims, "applications", "get", defaultTestObject))
// Now revoke the token by deleting the token
existingProj.Spec.Roles[0].JWTTokens = nil
existingProj.Status.JWTTokensByRole = nil
_, _ = s.AppClientset.ArgoprojV1alpha1().AppProjects(test.FakeArgoCDNamespace).Update(context.Background(), &existingProj, metav1.UpdateOptions{})
time.Sleep(200 * time.Millisecond) // this lets the informer get synced
assert.False(t, s.enf.Enforce(claims, "projects", "get", existingProj.ObjectMeta.Name))
assert.False(t, s.enf.Enforce(claims, "applications", "get", defaultTestObject))
}
func TestCertsAreNotGeneratedInInsecureMode(t *testing.T) {

View File

@@ -15,18 +15,26 @@ import (
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apiclient/version"
"github.com/argoproj/argo-cd/server/settings"
"github.com/argoproj/argo-cd/util/helm"
ksutil "github.com/argoproj/argo-cd/util/ksonnet"
"github.com/argoproj/argo-cd/util/kustomize"
"github.com/argoproj/argo-cd/util/log"
sessionmgr "github.com/argoproj/argo-cd/util/session"
)
type Server struct {
type server struct {
ksonnetVersion string
kustomizeVersion string
helmVersion string
kubectlVersion string
jsonnetVersion string
authenticator settings.Authenticator
disableAuth func() (bool, error)
}
func NewServer(authenticator settings.Authenticator, disableAuth func() (bool, error)) *server {
return &server{authenticator: authenticator, disableAuth: disableAuth}
}
func getVersion() (string, error) {
@@ -51,8 +59,17 @@ func getVersion() (string, error) {
}
// Version returns the version of the API server
func (s *Server) Version(context.Context, *empty.Empty) (*version.VersionMessage, error) {
func (s *server) Version(ctx context.Context, _ *empty.Empty) (*version.VersionMessage, error) {
vers := common.GetVersion()
disableAuth, err := s.disableAuth()
if err != nil {
return nil, err
}
if !sessionmgr.LoggedIn(ctx) && !disableAuth {
return &version.VersionMessage{Version: vers.Version}, nil
}
if s.ksonnetVersion == "" {
ksonnetVersion, err := ksutil.Version()
if err == nil {
@@ -104,6 +121,10 @@ func (s *Server) Version(context.Context, *empty.Empty) (*version.VersionMessage
}
// AuthFuncOverride allows the version to be returned without auth
func (s *Server) AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) {
func (s *server) AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) {
if s.authenticator != nil {
// this authenticates the user, but ignores any error, so that we have claims populated
ctx, _ = s.authenticator.Authenticate(ctx)
}
return ctx, nil
}

View File

@@ -1,6 +1,6 @@
controller: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true go run ./cmd/argocd-application-controller/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
api-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true go run ./cmd/argocd-server/main.go --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} --staticassets ui/dist/app"
dex: sh -c "test $ARGOCD_IN_CI = true && exit 0; go run github.com/argoproj/argo-cd/cmd/argocd-util 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 quay.io/dexidp/dex:v2.25.0 serve /dex.yaml"
dex: sh -c "test $ARGOCD_IN_CI = true && exit 0; go run github.com/argoproj/argo-cd/cmd/argocd-util 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.27.0 serve /dex.yaml"
redis: sh -c "/usr/local/bin/redis-server --save "" --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}"
repo-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} go run ./cmd/argocd-repo-server/main.go --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"
ui: sh -c "test $ARGOCD_IN_CI = true && exit 0; cd ui && ARGOCD_E2E_YARN_HOST=0.0.0.0 ${ARGOCD_E2E_YARN_CMD:-yarn} start"

View File

@@ -19,6 +19,7 @@ import (
"github.com/argoproj/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
networkingv1beta "k8s.io/api/networking/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -425,6 +426,15 @@ func TestAppWithSecrets(t *testing.T) {
diffOutput := FailOnErr(RunCli("app", "diff", app.Name)).(string)
assert.Empty(t, diffOutput)
// make sure resource update error does not print secret details
_, err = RunCli("app", "patch-resource", "test-app-with-secrets", "--resource-name", "test-secret",
"--kind", "Secret", "--patch", `{"op": "add", "path": "/data", "value": "hello"}'`,
"--patch-type", "application/json-patch+json")
require.Error(t, err)
assert.Contains(t, err.Error(), fmt.Sprintf("failed to patch Secret %s/test-secret", DeploymentNamespace()))
assert.NotContains(t, err.Error(), "username")
assert.NotContains(t, err.Error(), "password")
// patch secret and make sure app is out of sync and diff detects the change
FailOnErr(KubeClientset.CoreV1().Secrets(DeploymentNamespace()).Patch(context.Background(),
"test-secret", types.JSONPatchType, []byte(`[
@@ -1370,10 +1380,11 @@ func TestNamespaceAutoCreation(t *testing.T) {
func TestFailedSyncWithRetry(t *testing.T) {
Given(t).
Path(guestbookPath).
Path("hook").
When().
// app should be attempted to auto-synced once and marked with error after failed attempt detected
PatchFile("guestbook-ui-deployment.yaml", `[{"op": "replace", "path": "/spec/revisionHistoryLimit", "value": "badValue"}]`).
PatchFile("hook.yaml", `[{"op": "replace", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/hook": "PreSync"}}]`).
// make hook fail
PatchFile("hook.yaml", `[{"op": "replace", "path": "/spec/containers/0/command", "value": ["false"]}]`).
Create().
IgnoreErrors().
Sync("--retry-limit=1", "--retry-backoff-duration=1s").
@@ -1513,3 +1524,30 @@ definitions:
assert.Equal(t, "update-status", text)
})
}
func TestAppWaitOperationInProgress(t *testing.T) {
Given(t).
And(func() {
SetResourceOverrides(map[string]ResourceOverride{
"batch/Job": {
HealthLua: `return { status = 'Running' }`,
},
"apps/Deployment": {
HealthLua: `return { status = 'Suspended' }`,
},
})
}).
Async(true).
Path("hook-and-deployment").
When().
Create().
Sync().
Then().
// stuck in running state
Expect(OperationPhaseIs(OperationRunning)).
When().
And(func() {
_, err := RunCli("app", "wait", Name(), "--suspended")
errors.CheckError(err)
})
}

View File

@@ -4,6 +4,7 @@ import (
"testing"
. "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
. "github.com/argoproj/argo-cd/test/e2e/fixture"
. "github.com/argoproj/argo-cd/test/e2e/fixture/app"
"github.com/argoproj/gitops-engine/pkg/health"
@@ -16,23 +17,30 @@ func TestFixingDegradedApp(t *testing.T) {
When().
IgnoreErrors().
Create().
PatchFile("pod-1.yaml", `[{"op": "replace", "path": "/spec/containers/0/image", "value": "rubbish"}]`).
And(func() {
SetResourceOverrides(map[string]ResourceOverride{
"ConfigMap": {
HealthLua: `return { status = obj.metadata.annotations and obj.metadata.annotations['health'] or 'Degraded' }`,
},
})
}).
Sync().
Then().
Expect(OperationPhaseIs(OperationFailed)).
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
Expect(HealthIs(health.HealthStatusMissing)).
Expect(ResourceResultNumbering(1)).
Expect(ResourceSyncStatusIs("Pod", "pod-1", SyncStatusCodeSynced)).
Expect(ResourceHealthIs("Pod", "pod-1", health.HealthStatusDegraded)).
Expect(ResourceSyncStatusIs("Pod", "pod-2", SyncStatusCodeOutOfSync)).
Expect(ResourceHealthIs("Pod", "pod-2", health.HealthStatusMissing)).
Expect(ResourceSyncStatusIs("ConfigMap", "cm-1", SyncStatusCodeSynced)).
Expect(ResourceHealthIs("ConfigMap", "cm-1", health.HealthStatusDegraded)).
Expect(ResourceSyncStatusIs("ConfigMap", "cm-2", SyncStatusCodeOutOfSync)).
Expect(ResourceHealthIs("ConfigMap", "cm-2", health.HealthStatusMissing)).
When().
PatchFile("pod-1.yaml", `[{"op": "replace", "path": "/spec/containers/0/image", "value": "nginx:1.17.4-alpine"}]`).
PatchFile("cm-1.yaml", `[{"op": "replace", "path": "/metadata/annotations/health", "value": "Healthy"}]`).
PatchFile("cm-2.yaml", `[{"op": "replace", "path": "/metadata/annotations/health", "value": "Healthy"}]`).
// need to force a refresh here
Refresh(RefreshTypeNormal).
Then().
Expect(ResourceSyncStatusIs("Pod", "pod-1", SyncStatusCodeOutOfSync)).
Expect(ResourceSyncStatusIs("ConfigMap", "cm-1", SyncStatusCodeOutOfSync)).
When().
Sync().
Then().
@@ -40,10 +48,10 @@ func TestFixingDegradedApp(t *testing.T) {
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy)).
Expect(ResourceResultNumbering(2)).
Expect(ResourceSyncStatusIs("Pod", "pod-1", SyncStatusCodeSynced)).
Expect(ResourceHealthIs("Pod", "pod-1", health.HealthStatusHealthy)).
Expect(ResourceSyncStatusIs("Pod", "pod-2", SyncStatusCodeSynced)).
Expect(ResourceHealthIs("Pod", "pod-2", health.HealthStatusHealthy))
Expect(ResourceSyncStatusIs("ConfigMap", "cm-1", SyncStatusCodeSynced)).
Expect(ResourceHealthIs("ConfigMap", "cm-1", health.HealthStatusHealthy)).
Expect(ResourceSyncStatusIs("ConfigMap", "cm-2", SyncStatusCodeSynced)).
Expect(ResourceHealthIs("ConfigMap", "cm-2", health.HealthStatusHealthy))
}
func TestOneProgressingDeploymentIsSucceededAndSynced(t *testing.T) {

View File

@@ -0,0 +1,6 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-1
annotations:
argocd.argoproj.io/sync-wave: "1"

View File

@@ -0,0 +1,6 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-2
annotations:
argocd.argoproj.io/sync-wave: "2"

View File

@@ -1,11 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-1
annotations:
argocd.argoproj.io/sync-wave: "1"
spec:
containers:
- name: main
image: nginx:1.17.4-alpine
imagePullPolicy: IfNotPresent

View File

@@ -1,11 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-2
annotations:
argocd.argoproj.io/sync-wave: "2"
spec:
containers:
- name: main
image: nginx:1.17.4-alpine
imagePullPolicy: IfNotPresent

View File

@@ -1,14 +1,19 @@
package test
import (
"context"
"github.com/argoproj/gitops-engine/pkg/utils/testing"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
apps "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake"
appclient "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/typed/application/v1alpha1"
appinformer "github.com/argoproj/argo-cd/pkg/client/informers/externalversions"
applister "github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
)
@@ -114,6 +119,30 @@ func NewFakeSecret(policy ...string) *apiv1.Secret {
return &secret
}
type interfaceLister struct {
appProjects appclient.AppProjectInterface
}
func (l interfaceLister) List(selector labels.Selector) ([]*v1alpha1.AppProject, error) {
res, err := l.appProjects.List(context.Background(), metav1.ListOptions{LabelSelector: selector.String()})
if err != nil {
return nil, err
}
items := make([]*v1alpha1.AppProject, len(res.Items))
for i := range res.Items {
items[i] = &res.Items[i]
}
return items, nil
}
func (l interfaceLister) Get(name string) (*v1alpha1.AppProject, error) {
return l.appProjects.Get(context.Background(), name, metav1.GetOptions{})
}
func NewFakeProjListerFromInterface(appProjects appclient.AppProjectInterface) applister.AppProjectNamespaceLister {
return &interfaceLister{appProjects: appProjects}
}
func NewFakeProjLister(objects ...runtime.Object) applister.AppProjectNamespaceLister {
fakeAppClientset := apps.NewSimpleClientset(objects...)
factory := appinformer.NewFilteredSharedInformerFactory(fakeAppClientset, 0, "", func(options *metav1.ListOptions) {})

View File

@@ -1,7 +1,7 @@
import {AutocompleteField, DropDownMenu, FormField, FormSelect, HelpIcon, PopupApi} from 'argo-ui';
import * as React from 'react';
import {FormApi, Text} from 'react-form';
import {Cluster, DataLoader, EditablePanel, EditablePanelItem, Expandable, MapInputField, Repo, Revision, RevisionHelpIcon} from '../../../shared/components';
import {Cluster, DataLoader, EditablePanel, EditablePanelItem, Expandable, MapInputField, NumberField, Repo, Revision, RevisionHelpIcon} from '../../../shared/components';
import {BadgePanel, Spinner} from '../../../shared/components';
import {Consumer} from '../../../shared/context';
import * as models from '../../../shared/models';
@@ -202,7 +202,7 @@ export const ApplicationSummary = (props: {app: models.Application; updateApp: (
view: app.spec.revisionHistoryLimit,
edit: (formApi: FormApi) => (
<div style={{position: 'relative'}}>
<FormField formApi={formApi} field='spec.revisionHistoryLimit' component={Text} />
<FormField formApi={formApi} field='spec.revisionHistoryLimit' componentProps={{style: {paddingRight: '1em'}, placeholder: '10'}} component={NumberField} />
<div style={{position: 'absolute', right: '0', top: '0'}}>
<HelpIcon
title='This limits this number of items kept in the apps revision history.

View File

@@ -23,3 +23,4 @@ export * from './revision';
export * from './timestamp';
export * from './spinner';
export * from './badge-panel/badge-panel';
export * from './number-field';

View File

@@ -0,0 +1,11 @@
import * as React from 'react';
import * as ReactForm from 'react-form';
export const NumberField = ReactForm.FormField((props: {fieldApi: ReactForm.FieldApi; className: string}) => {
const {
fieldApi: {getValue, setValue},
...rest
} = props;
return <input {...rest} className={props.className} type='number' value={getValue()} onChange={el => setValue(parseInt(el.target.value, 10))} />;
});

View File

@@ -4,7 +4,7 @@ import * as React from 'react';
import {BehaviorSubject, Observable} from 'rxjs';
import {AppContext} from '../context';
import {services} from '../services';
import requests from '../services/requests';
const mostRecentLoggedIn = new BehaviorSubject<boolean>(false);
function isLoggedIn(): Observable<boolean> {
@@ -51,9 +51,10 @@ export class Page extends React.Component<{title: string; toolbar?: Toolbar | Ob
private async goToLogin(logout = false) {
if (logout) {
await services.users.logout();
window.location.href = requests.toAbsURL('/auth/logout');
} else {
this.appContext.history.push('/login');
}
this.appContext.history.push('/login');
}
private get appContext(): AppContext {

View File

@@ -22,7 +22,7 @@ export class ClustersService {
public update(cluster: models.Cluster, ...paths: string[]): Promise<models.Cluster> {
return requests
.put(`/clusters/${encodeURIComponent(cluster.server)}`)
.query({updatedPaths: paths})
.query({updatedFields: paths})
.send(cluster)
.then(res => res.body as models.Cluster);
}

View File

@@ -131,12 +131,8 @@ func TestRepoWithKnownType(repo *argoappv1.Repository, isHelm bool, isHelmOci bo
} else {
repo.Type = "git"
}
repo.EnableOCI = repo.EnableOCI || isHelmOci
if isHelmOci {
repo.EnableOCI = true
} else {
repo.EnableOCI = false
}
return TestRepo(repo)
}

View File

@@ -3,6 +3,7 @@ package appstate
import (
"context"
"fmt"
"sort"
"time"
"github.com/go-redis/redis/v8"
@@ -61,6 +62,9 @@ func (c *Cache) GetAppManagedResources(appName string, res *[]*appv1.ResourceDif
}
func (c *Cache) SetAppManagedResources(appName string, managedResources []*appv1.ResourceDiff) error {
sort.Slice(managedResources, func(i, j int) bool {
return managedResources[i].FullName() < managedResources[j].FullName()
})
return c.SetItem(appManagedResourcesKey(appName), managedResources, c.appStateCacheExpiration, managedResources == nil)
}
@@ -82,6 +86,9 @@ func (c *Cache) OnAppResourcesTreeChanged(ctx context.Context, appName string, c
}
func (c *Cache) SetAppResourcesTree(appName string, resourcesTree *appv1.ApplicationTree) error {
if resourcesTree != nil {
resourcesTree.Normalize()
}
err := c.SetItem(appResourcesTreeKey(appName), resourcesTree, c.appStateCacheExpiration, resourcesTree == nil)
if err != nil {
return err

14
util/cache/cache.go vendored
View File

@@ -78,14 +78,22 @@ type Cache struct {
client CacheClient
}
func (c *Cache) GetClient() CacheClient {
return c.client
}
func (c *Cache) SetClient(client CacheClient) {
c.client = client
}
func (c *Cache) SetItem(key string, item interface{}, expiration time.Duration, delete bool) error {
if item == nil {
return fmt.Errorf("cannot set item to nil for key %s", key)
}
key = fmt.Sprintf("%s|%s", key, common.CacheVersion)
if delete {
return c.client.Delete(key)
} else {
if item == nil {
return fmt.Errorf("cannot set item to nil for key %s", key)
}
return c.client.Set(&Item{Object: item, Key: key, Expiration: expiration})
}
}

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/gob"
"fmt"
"time"
gocache "github.com/patrickmn/go-cache"
@@ -29,6 +30,25 @@ func (i *InMemoryCache) Set(item *Item) error {
return nil
}
// HasSame returns true if key with the same value already present in cache
func (i *InMemoryCache) HasSame(key string, obj interface{}) (bool, error) {
var buf bytes.Buffer
err := gob.NewEncoder(&buf).Encode(obj)
if err != nil {
return false, err
}
bufIf, found := i.memCache.Get(key)
if !found {
return false, nil
}
existingBuf, ok := bufIf.(bytes.Buffer)
if !ok {
panic(fmt.Errorf("InMemoryCache has unexpected entry: %v", existingBuf))
}
return bytes.Equal(buf.Bytes(), existingBuf.Bytes()), nil
}
func (i *InMemoryCache) Get(key string, obj interface{}) error {
bufIf, found := i.memCache.Get(key)
if !found {

19
util/cache/redis.go vendored
View File

@@ -2,6 +2,7 @@ package cache
import (
"context"
"encoding/json"
"time"
ioutil "github.com/argoproj/argo-cd/util/io"
@@ -30,20 +31,28 @@ func (r *redisCache) Set(item *Item) error {
expiration = r.expiration
}
val, err := json.Marshal(item.Object)
if err != nil {
return err
}
return r.cache.Set(&rediscache.Item{
Key: item.Key,
Value: item.Object,
Value: val,
TTL: expiration,
})
}
func (r *redisCache) Get(key string, obj interface{}) error {
err := r.cache.Get(context.TODO(), key, obj)
var data []byte
err := r.cache.Get(context.TODO(), key, &data)
if err == rediscache.ErrCacheMiss {
return ErrCacheMiss
err = ErrCacheMiss
}
return err
if err != nil {
return err
}
return json.Unmarshal(data, obj)
}
func (r *redisCache) Delete(key string) error {

68
util/cache/twolevelclient.go vendored Normal file
View File

@@ -0,0 +1,68 @@
package cache
import (
"context"
"time"
log "github.com/sirupsen/logrus"
)
// NewTwoLevelClient creates cache client that proxies requests to given external cache and tries to minimize
// number of requests to external client by storing cache entries in local in-memory cache.
func NewTwoLevelClient(client CacheClient, inMemoryExpiration time.Duration) *twoLevelClient {
return &twoLevelClient{inMemoryCache: NewInMemoryCache(inMemoryExpiration), externalCache: client}
}
type twoLevelClient struct {
inMemoryCache *InMemoryCache
externalCache CacheClient
}
// Set stores the given value in both in-memory and external cache.
// Skip storing the value in external cache if the same value already exists in memory to avoid requesting external cache.
func (c *twoLevelClient) Set(item *Item) error {
has, err := c.inMemoryCache.HasSame(item.Key, item.Object)
if has {
return nil
}
if err != nil {
log.Warnf("Failed to check key '%s' in in-memory cache: %v", item.Key, err)
}
err = c.inMemoryCache.Set(item)
if err != nil {
log.Warnf("Failed to save key '%s' in in-memory cache: %v", item.Key, err)
}
return c.externalCache.Set(item)
}
// Get returns cache value from in-memory cache if it present. Otherwise loads it from external cache and persists
// in memory to avoid future requests to external cache.
func (c *twoLevelClient) Get(key string, obj interface{}) error {
err := c.inMemoryCache.Get(key, obj)
if err == nil {
return nil
}
err = c.externalCache.Get(key, obj)
if err == nil {
_ = c.inMemoryCache.Set(&Item{Key: key, Object: obj})
}
return err
}
// Delete deletes cache for given key in both in-memory and external cache.
func (c *twoLevelClient) Delete(key string) error {
err := c.inMemoryCache.Delete(key)
if err != nil {
return err
}
return c.externalCache.Delete(key)
}
func (c *twoLevelClient) OnUpdated(ctx context.Context, key string, callback func() error) error {
return c.externalCache.OnUpdated(ctx, key, callback)
}
func (c *twoLevelClient) NotifyUpdated(key string) error {
return c.externalCache.NotifyUpdated(key)
}

View File

@@ -6,7 +6,7 @@ import (
"strings"
"time"
jwt "github.com/dgrijalva/jwt-go"
jwt "github.com/dgrijalva/jwt-go/v4"
log "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
@@ -296,14 +296,14 @@ type ServiceAccountClaims struct {
}
// Valid satisfies the jwt.Claims interface to enable JWT parsing
func (sac *ServiceAccountClaims) Valid() error {
func (sac *ServiceAccountClaims) Valid(helper *jwt.ValidationHelper) error {
return nil
}
// ParseServiceAccountToken parses a Kubernetes service account token
func ParseServiceAccountToken(token string) (*ServiceAccountClaims, error) {
parser := &jwt.Parser{
SkipClaimsValidation: true,
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation(), jwt.WithoutAudienceValidation()),
}
var claims ServiceAccountClaims
_, _, err := parser.ParseUnverified(token, &claims)

View File

@@ -53,6 +53,7 @@ func GenerateDexConfigYAML(settings *settings.ArgoCDSettings) ([]byte, error) {
"public": true,
"redirectURIs": []string{
"http://localhost",
"http://localhost:8085/auth/callback",
},
}

View File

@@ -9,9 +9,9 @@ import (
// ServeHealthCheck serves the health check endpoint.
// ServeHealthCheck relies on the provided function to return an error if unhealthy and nil otherwise.
func ServeHealthCheck(mux *http.ServeMux, f func() error) {
func ServeHealthCheck(mux *http.ServeMux, f func(r *http.Request) error) {
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
if err := f(); err != nil {
if err := f(r); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
log.Errorln(w, err)
} else {

View File

@@ -21,7 +21,7 @@ func TestHealthCheck(t *testing.T) {
c <- listener.Addr().String()
mux := http.NewServeMux()
ServeHealthCheck(mux, func() error {
ServeHealthCheck(mux, func(r *http.Request) error {
if sentinel {
return fmt.Errorf("This is a dummy error")
}

View File

@@ -90,7 +90,7 @@ func (c *nativeHelmChart) ensureHelmChartRepoPath() error {
}
func (c *nativeHelmChart) CleanChartCache(chart string, version *semver.Version) error {
return os.RemoveAll(c.getChartPath(chart, version))
return os.RemoveAll(c.getCachedChartPath(chart, version))
}
func (c *nativeHelmChart) ExtractChart(chart string, version *semver.Version) (string, io.Closer, error) {
@@ -98,77 +98,99 @@ func (c *nativeHelmChart) ExtractChart(chart string, version *semver.Version) (s
if err != nil {
return "", nil, err
}
chartPath := c.getChartPath(chart, version)
c.repoLock.Lock(chartPath)
defer c.repoLock.Unlock(chartPath)
// always use Helm V3 since we don't have chart content to determine correct Helm version
helmCmd, err := NewCmdWithVersion(c.repoPath, HelmV3, c.enableOci)
exists, err := fileExist(chartPath)
if err != nil {
return "", nil, err
}
defer helmCmd.Close()
_, err = helmCmd.Init()
if err != nil {
return "", nil, err
}
// throw away temp directory that stores extracted chart and should be deleted as soon as no longer needed by returned closer
tempDir, err := ioutil.TempDir("", "helm")
if err != nil {
return "", nil, err
}
cachedChartPath := c.getCachedChartPath(chart, version)
c.repoLock.Lock(cachedChartPath)
defer c.repoLock.Unlock(cachedChartPath)
// check if chart tar is already downloaded
exists, err := fileExist(cachedChartPath)
if err != nil {
return "", nil, err
}
if !exists {
// always use Helm V3 since we don't have chart content to determine correct Helm version
helmCmd, err := NewCmdWithVersion(c.repoPath, HelmV3, c.enableOci)
if err != nil {
return "", nil, err
}
defer helmCmd.Close()
_, err = helmCmd.Init()
if err != nil {
return "", nil, err
}
if c.enableOci {
_, err = helmCmd.Login(c.repoURL, c.creds)
if err != nil {
return "", nil, err
}
defer func() {
_, _ = helmCmd.Logout(c.repoURL, c.creds)
}()
}
// (1) because `helm fetch` downloads an arbitrary file name, we download to an empty temp directory
// create empty temp directory to extract chart from the registry
tempDest, err := ioutil.TempDir("", "helm")
if err != nil {
return "", nil, err
}
defer func() { _ = os.RemoveAll(tempDest) }()
_, err = helmCmd.Fetch(c.repoURL, chart, version.String(), tempDest, c.creds)
if err != nil {
return "", nil, err
}
if c.enableOci {
_, err = helmCmd.Export(c.repoURL, chart, version.String(), tempDest)
if c.creds.Password != "" && c.creds.Username != "" {
_, err = helmCmd.Login(c.repoURL, c.creds)
if err != nil {
return "", nil, err
}
defer func() {
_, _ = helmCmd.Logout(c.repoURL, c.creds)
}()
}
// 'helm chart pull' ensures that chart is downloaded into local repository cache
_, err = helmCmd.ChartPull(c.repoURL, chart, version.String())
if err != nil {
return "", nil, err
}
// 'helm chart export' copies cached chart into temp directory
_, err = helmCmd.ChartExport(c.repoURL, chart, version.String(), tempDest)
if err != nil {
return "", nil, err
}
// use downloaded chart content to produce tar file in expected cache location
cmd := exec.Command("tar", "-zcvf", cachedChartPath, normalizeChartName(chart))
cmd.Dir = tempDest
_, err = executil.Run(cmd)
if err != nil {
return "", nil, err
}
} else {
_, err = helmCmd.Fetch(c.repoURL, chart, version.String(), tempDest, c.creds)
if err != nil {
return "", nil, err
}
// 'helm fetch' file downloads chart into the tgz file and we move that to where we want it
infos, err := ioutil.ReadDir(tempDest)
if err != nil {
return "", nil, err
}
if len(infos) != 1 {
return "", nil, fmt.Errorf("expected 1 file, found %v", len(infos))
}
err = os.Rename(filepath.Join(tempDest, infos[0].Name()), cachedChartPath)
if err != nil {
return "", nil, err
}
}
// (2) then we assume that the only file downloaded into the directory is the tgz file
// and we move that to where we want it
infos, err := ioutil.ReadDir(tempDest)
if err != nil {
return "", nil, err
}
if len(infos) != 1 {
return "", nil, fmt.Errorf("expected 1 file, found %v", len(infos))
}
err = os.Rename(filepath.Join(tempDest, infos[0].Name()), chartPath)
if err != nil {
return "", nil, err
}
}
// untar helm chart into throw away temp directory which should be deleted as soon as no longer needed
tempDir, err := ioutil.TempDir("", "helm")
if err != nil {
return "", nil, err
}
cmd := exec.Command("tar", "-zxvf", chartPath)
cmd := exec.Command("tar", "-zxvf", cachedChartPath)
cmd.Dir = tempDir
_, err = executil.Run(cmd)
if err != nil {
@@ -214,16 +236,19 @@ func (c *nativeHelmChart) TestHelmOCI() (bool, error) {
}
defer helmCmd.Close()
_, err = helmCmd.Login(c.repoURL, c.creds)
if err != nil {
return false, err
// Looks like there is no good way to test access to OCI repo if credentials are not provided
// just assume it is accessible
if c.creds.Username != "" && c.creds.Password != "" {
_, err = helmCmd.Login(c.repoURL, c.creds)
if err != nil {
return false, err
}
defer func() {
_, _ = helmCmd.Logout(c.repoURL, c.creds)
}()
log.WithFields(log.Fields{"seconds": time.Since(start).Seconds()}).Info("took to test helm oci repository")
}
defer func() {
_, _ = helmCmd.Logout(c.repoURL, c.creds)
}()
log.WithFields(log.Fields{"seconds": time.Since(start).Seconds()}).Info("took to test helm oci repository")
return true, nil
}
@@ -293,6 +318,7 @@ func newTLSConfig(creds Creds) (*tls.Config, error) {
// Normalize a chart name for file system use, that is, if chart name is foo/bar/baz, returns the last component as chart name.
func normalizeChartName(chart string) string {
strings.Join(strings.Split(chart, "/"), "_")
_, nc := path.Split(chart)
// We do not want to return the empty string or something else related to filesystem access
// Instead, return original string
@@ -302,17 +328,11 @@ func normalizeChartName(chart string) string {
return nc
}
func (c *nativeHelmChart) getChartPath(chart string, version *semver.Version) string {
if repoNamespace, chartName, isHelmOci := IsHelmOci(chart); isHelmOci {
return path.Join(c.repoPath, fmt.Sprintf("%s-%s-%v.tgz", repoNamespace, normalizeChartName(chartName), version))
}
return path.Join(c.repoPath, fmt.Sprintf("%s-%v.tgz", normalizeChartName(chart), version))
func (c *nativeHelmChart) getCachedChartPath(chart string, version *semver.Version) string {
return path.Join(c.repoPath, fmt.Sprintf("%s-%v.tgz", strings.ReplaceAll(chart, "/", "_"), version))
}
func IsHelmOci(chart string) (string, string, bool) {
chartArray := strings.Split(chart, "/")
if len(chartArray) == 2 {
return chartArray[0], chartArray[1], true
}
return "", "", false
// Only OCI registries support storing charts under sub-directories.
func IsHelmOciChart(chart string) bool {
return strings.Contains(chart, "/")
}

View File

@@ -211,26 +211,18 @@ func writeToTmp(data []byte) (string, io.Closer, error) {
}
func (c *Cmd) Fetch(repo, chartName, version, destination string, creds Creds) (string, error) {
args := []string{}
if _, _, isHelmOci := IsHelmOci(chartName); isHelmOci {
args = append(args, "chart", "pull")
repoUrl := fmt.Sprintf(repo + "/" + chartName + ":" + version)
args = append(args, repoUrl)
} else {
args = append(args, c.pullCommand, "--destination", destination)
if version != "" {
args = append(args, "--version", version)
}
if creds.Username != "" {
args = append(args, "--username", creds.Username)
}
if creds.Password != "" {
args = append(args, "--password", creds.Password)
}
args = append(args, "--repo", repo, chartName)
args := []string{c.pullCommand, "--destination", destination}
if version != "" {
args = append(args, "--version", version)
}
if creds.Username != "" {
args = append(args, "--username", creds.Username)
}
if creds.Password != "" {
args = append(args, "--password", creds.Password)
}
args = append(args, "--repo", repo, chartName)
if creds.CAPath != "" {
args = append(args, "--ca-file", creds.CAPath)
@@ -255,29 +247,16 @@ func (c *Cmd) Fetch(repo, chartName, version, destination string, creds Creds) (
return c.run(args...)
}
func (c *Cmd) Export(repo, chartName, version, destination string) (string, error) {
output := ""
var err error
func (c *Cmd) ChartPull(repo string, chart string, version string) (string, error) {
return c.run("chart", "pull", fmt.Sprintf("%s/%s:%s", repo, chart, version))
}
func (c *Cmd) ChartExport(repo string, chartName string, version string, destination string) (string, error) {
args := []string{"chart", "export"}
repoUrl := fmt.Sprintf(repo + "/" + chartName + ":" + version)
args = append(args, repoUrl, "--destination", destination)
chartURL := fmt.Sprintf(repo + "/" + chartName + ":" + version)
args = append(args, chartURL, "--destination", destination)
output, err = c.run(args...)
if err != nil {
return "", err
}
// tar helm chart
repoNamespace, repoName, _ := IsHelmOci(chartName)
cmd := exec.Command("tar", "-zcvf", repoNamespace+"-"+repoName+"-"+version+".tgz", repoName)
cmd.Dir = destination
_, err = executil.Run(cmd)
if err != nil {
return "", err
}
defer func() { _ = os.RemoveAll(destination + "/" + repoName) }()
return output, nil
return c.run(args...)
}
func (c *Cmd) dependencyBuild() (string, error) {

View File

@@ -2,9 +2,11 @@ package jwt
import (
"encoding/json"
"errors"
"fmt"
"time"
jwtgo "github.com/dgrijalva/jwt-go"
jwtgo "github.com/dgrijalva/jwt-go/v4"
)
// MapClaims converts a jwt.Claims to a MapClaims
@@ -21,8 +23,8 @@ func MapClaims(claims jwtgo.Claims) (jwtgo.MapClaims, error) {
return mapClaims, nil
}
// GetField extracts a field from the claims as a string
func GetField(claims jwtgo.MapClaims, fieldName string) string {
// StringField extracts a field from the claims as a string
func StringField(claims jwtgo.MapClaims, fieldName string) string {
if fieldIf, ok := claims[fieldName]; ok {
if field, ok := fieldIf.(string); ok {
return field
@@ -31,6 +33,16 @@ func GetField(claims jwtgo.MapClaims, fieldName string) string {
return ""
}
// Float64Field extracts a field from the claims as a float64
func Float64Field(claims jwtgo.MapClaims, fieldName string) float64 {
if fieldIf, ok := claims[fieldName]; ok {
if field, ok := fieldIf.(float64); ok {
return field
}
}
return 0
}
// GetScopeValues extracts the values of specified scopes from the claims
func GetScopeValues(claims jwtgo.MapClaims, scopes []string) []string {
groups := make([]string, 0)
@@ -67,9 +79,13 @@ func GetID(m jwtgo.MapClaims) (string, error) {
return "", fmt.Errorf("jti '%v' is not a string", m["jti"])
}
// GetIssuedAt returns the issued at as an int64
func GetIssuedAt(m jwtgo.MapClaims) (int64, error) {
switch iat := m["iat"].(type) {
// IssuedAt returns the issued at as an int64
func IssuedAt(m jwtgo.MapClaims) (int64, error) {
iatField, ok := m["iat"]
if !ok {
return 0, errors.New("token does not have iat claim")
}
switch iat := iatField.(type) {
case float64:
return int64(iat), nil
case json.Number:
@@ -81,6 +97,12 @@ func GetIssuedAt(m jwtgo.MapClaims) (int64, error) {
}
}
// IssuedAtTime returns the issued at as a time.Time
func IssuedAtTime(m jwtgo.MapClaims) (time.Time, error) {
iat, err := IssuedAt(m)
return time.Unix(iat, 0), err
}
func Claims(in interface{}) jwtgo.Claims {
claims, ok := in.(jwtgo.Claims)
if ok {

View File

@@ -1,9 +1,11 @@
package jwt
import (
"fmt"
"testing"
"time"
jwt "github.com/dgrijalva/jwt-go"
jwt "github.com/dgrijalva/jwt-go/v4"
"github.com/stretchr/testify/assert"
)
@@ -36,3 +38,25 @@ func TestGetGroups(t *testing.T) {
assert.Empty(t, GetGroups(jwt.MapClaims{}, []string{"groups"}))
assert.Equal(t, []string{"foo"}, GetGroups(jwt.MapClaims{"groups": []string{"foo"}}, []string{"groups"}))
}
func TestIssuedAtTime_Int64(t *testing.T) {
// Tuesday, 1 December 2020 14:00:00
claims := jwt.MapClaims{"iat": int64(1606831200)}
issuedAt, err := IssuedAtTime(claims)
assert.Nil(t, err)
str := fmt.Sprint(issuedAt.UTC().Format("Mon Jan _2 15:04:05 2006"))
assert.Equal(t, "Tue Dec 1 14:00:00 2020", str)
}
func TestIssuedAtTime_Error_NoInt(t *testing.T) {
claims := jwt.MapClaims{"iat": 1606831200}
_, err := IssuedAtTime(claims)
assert.NotNil(t, err)
}
func TestIssuedAtTime_Error_Missing(t *testing.T) {
claims := jwt.MapClaims{}
iat, err := IssuedAtTime(claims)
assert.NotNil(t, err)
assert.Equal(t, time.Unix(0, 0), iat)
}

View File

@@ -6,7 +6,7 @@ import (
"os/user"
"path"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/v4"
configUtil "github.com/argoproj/argo-cd/util/config"
)
@@ -64,7 +64,7 @@ type User struct {
// Claims returns the standard claims from the JWT claims
func (u *User) Claims() (*jwt.StandardClaims, error) {
parser := &jwt.Parser{
SkipClaimsValidation: true,
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation(), jwt.WithoutAudienceValidation()),
}
claims := jwt.StandardClaims{}
_, _, err := parser.ParseUnverified(u.AuthToken, &claims)

View File

@@ -15,7 +15,7 @@ import (
"github.com/argoproj/pkg/jwt/zjwt"
gooidc "github.com/coreos/go-oidc"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/v4"
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2"

View File

@@ -14,7 +14,7 @@ import (
"github.com/casbin/casbin"
"github.com/casbin/casbin/model"
jwt "github.com/dgrijalva/jwt-go"
jwt "github.com/dgrijalva/jwt-go/v4"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@@ -132,19 +132,12 @@ func (e *Enforcer) EnforceErr(rvals ...interface{}) error {
if err != nil {
break
}
sub := jwtutil.GetField(claims, "sub")
if sub != "" {
if sub := jwtutil.StringField(claims, "sub"); sub != "" {
rvalsStrs = append(rvalsStrs, fmt.Sprintf("sub: %s", sub))
}
iatField, ok := claims["iat"]
if !ok {
break
if issuedAtTime, err := jwtutil.IssuedAtTime(claims); err == nil {
rvalsStrs = append(rvalsStrs, fmt.Sprintf("iat: %s", issuedAtTime.Format(time.RFC3339)))
}
iat, ok := iatField.(float64)
if !ok {
break
}
rvalsStrs = append(rvalsStrs, fmt.Sprintf("iat: %s", time.Unix(int64(iat), 0).Format(time.RFC3339)))
}
errMsg = fmt.Sprintf("%s: %s", errMsg, strings.Join(rvalsStrs, ", "))
}

View File

@@ -6,7 +6,7 @@ import (
"testing"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/v4"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
apiv1 "k8s.io/api/core/v1"
@@ -318,19 +318,19 @@ func TestEnforceErrorMessage(t *testing.T) {
iat := time.Unix(int64(1593035962), 0).Format(time.RFC3339)
exp := fmt.Sprintf("rpc error: code = PermissionDenied desc = permission denied: project, sub: proj:default:admin, iat: %s", iat)
// nolint:staticcheck
ctx = context.WithValue(context.Background(), "claims", &jwt.StandardClaims{Subject: "proj:default:admin", IssuedAt: 1593035962})
ctx = context.WithValue(context.Background(), "claims", &jwt.StandardClaims{Subject: "proj:default:admin", IssuedAt: jwt.NewTime(1593035962)})
err = enf.EnforceErr(ctx.Value("claims"), "project")
assert.Error(t, err)
assert.Equal(t, exp, err.Error())
// nolint:staticcheck
ctx = context.WithValue(context.Background(), "claims", &jwt.StandardClaims{ExpiresAt: 1})
ctx = context.WithValue(context.Background(), "claims", &jwt.StandardClaims{ExpiresAt: jwt.NewTime(1)})
err = enf.EnforceErr(ctx.Value("claims"), "project")
assert.Error(t, err)
assert.Equal(t, "rpc error: code = PermissionDenied desc = permission denied: project", err.Error())
// nolint:staticcheck
ctx = context.WithValue(context.Background(), "claims", &jwt.StandardClaims{Subject: "proj:default:admin", IssuedAt: 0})
ctx = context.WithValue(context.Background(), "claims", &jwt.StandardClaims{Subject: "proj:default:admin", IssuedAt: nil})
err = enf.EnforceErr(ctx.Value("claims"), "project")
assert.Error(t, err)
assert.Equal(t, "rpc error: code = PermissionDenied desc = permission denied: project, sub: proj:default:admin", err.Error())

View File

@@ -2,6 +2,7 @@ package session
import (
"context"
"encoding/json"
"errors"
"fmt"
"math"
@@ -11,12 +12,14 @@ import (
"os"
"time"
"github.com/dgrijalva/jwt-go"
oidc "github.com/coreos/go-oidc"
"github.com/dgrijalva/jwt-go/v4"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
"github.com/argoproj/argo-cd/server/rbacpolicy"
"github.com/argoproj/argo-cd/util/cache/appstate"
"github.com/argoproj/argo-cd/util/dex"
@@ -31,6 +34,7 @@ import (
// SessionManager generates and validates JWT tokens for login sessions.
type SessionManager struct {
settingsMgr *settings.SettingsManager
projectsLister v1alpha1.AppProjectNamespaceLister
client *http.Client
prov oidcutil.Provider
storage UserStateStorage
@@ -127,11 +131,12 @@ func getLoginFailureWindow() time.Duration {
}
// NewSessionManager creates a new session manager from Argo CD settings
func NewSessionManager(settingsMgr *settings.SettingsManager, dexServerAddr string, storage UserStateStorage) *SessionManager {
func NewSessionManager(settingsMgr *settings.SettingsManager, projectsLister v1alpha1.AppProjectNamespaceLister, dexServerAddr string, storage UserStateStorage) *SessionManager {
s := SessionManager{
settingsMgr: settingsMgr,
storage: storage,
sleep: time.Sleep,
projectsLister: projectsLister,
verificationDelayNoiseEnabled: true,
}
settings, err := settingsMgr.GetSettings()
@@ -172,28 +177,62 @@ func (mgr *SessionManager) Create(subject string, secondsBeforeExpiry int64, id
// you would like it to contain.
now := time.Now().UTC()
claims := jwt.StandardClaims{
IssuedAt: now.Unix(),
IssuedAt: jwt.At(now),
Issuer: SessionManagerClaimsIssuer,
NotBefore: now.Unix(),
NotBefore: jwt.At(now),
Subject: subject,
Id: id,
ID: id,
}
if secondsBeforeExpiry > 0 {
expires := now.Add(time.Duration(secondsBeforeExpiry) * time.Second)
claims.ExpiresAt = expires.Unix()
claims.ExpiresAt = jwt.At(expires)
}
return mgr.signClaims(claims)
}
type standardClaims struct {
Audience jwt.ClaimStrings `json:"aud,omitempty"`
ExpiresAt int64 `json:"exp,omitempty"`
ID string `json:"jti,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
Issuer string `json:"iss,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Subject string `json:"sub,omitempty"`
}
func unixTimeOrZero(t *jwt.Time) int64 {
if t == nil {
return 0
}
return t.Unix()
}
func (mgr *SessionManager) signClaims(claims jwt.Claims) (string, error) {
log.Infof("Issuing claims: %v", claims)
// log.Infof("Issuing claims: %v", claims)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
settings, err := mgr.settingsMgr.GetSettings()
if err != nil {
return "", err
}
return token.SignedString(settings.ServerSignature)
// workaround for https://github.com/argoproj/argo-cd/issues/5217
// According to https://tools.ietf.org/html/rfc7519#section-4.1.6 "iat" and other time fields must contain
// number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time.
// The https://github.com/dgrijalva/jwt-go marshals time as non integer.
return token.SignedString(settings.ServerSignature, jwt.WithMarshaller(func(ctx jwt.CodingContext, v interface{}) ([]byte, error) {
if std, ok := v.(jwt.StandardClaims); ok {
return json.Marshal(standardClaims{
Audience: std.Audience,
ExpiresAt: unixTimeOrZero(std.ExpiresAt),
ID: std.ID,
IssuedAt: unixTimeOrZero(std.IssuedAt),
Issuer: std.Issuer,
NotBefore: unixTimeOrZero(std.NotBefore),
Subject: std.Subject,
})
}
return json.Marshal(v)
}))
}
// Parse tries to parse the provided string and returns the token claims for local login.
@@ -203,7 +242,7 @@ func (mgr *SessionManager) Parse(tokenString string) (jwt.Claims, error) {
// head of the token to identify which key to use, but the parsed token (head and claims) is provided
// to the callback, providing flexibility.
var claims jwt.MapClaims
settings, err := mgr.settingsMgr.GetSettings()
argoCDSettings, err := mgr.settingsMgr.GetSettings()
if err != nil {
return nil, err
}
@@ -212,14 +251,30 @@ func (mgr *SessionManager) Parse(tokenString string) (jwt.Claims, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return settings.ServerSignature, nil
return argoCDSettings.ServerSignature, nil
})
if err != nil {
return nil, err
}
subject := jwtutil.GetField(claims, "sub")
if rbacpolicy.IsProjectSubject(subject) {
issuedAt, err := jwtutil.IssuedAtTime(claims)
if err != nil {
return nil, err
}
subject := jwtutil.StringField(claims, "sub")
id := jwtutil.StringField(claims, "jti")
if projName, role, ok := rbacpolicy.GetProjectRoleFromSubject(subject); ok {
proj, err := mgr.projectsLister.Get(projName)
if err != nil {
return nil, err
}
_, _, err = proj.GetJWTToken(role, issuedAt.Unix(), id)
if err != nil {
return nil, err
}
return token.Claims, nil
}
@@ -228,11 +283,24 @@ func (mgr *SessionManager) Parse(tokenString string) (jwt.Claims, error) {
return nil, err
}
if id := jwtutil.GetField(claims, "jti"); id != "" && account.TokenIndex(id) == -1 {
if !account.Enabled {
return nil, fmt.Errorf("account %s is disabled", subject)
}
var capability settings.AccountCapability
if id != "" {
capability = settings.AccountCapabilityApiKey
} else {
capability = settings.AccountCapabilityLogin
}
if !account.HasCapability(capability) {
return nil, fmt.Errorf("account %s does not have '%s' capability", subject, capability)
}
if id != "" && account.TokenIndex(id) == -1 {
return nil, fmt.Errorf("account %s does not have token with id %s", subject, id)
}
issuedAt := time.Unix(int64(claims["iat"].(float64)), 0)
if account.PasswordMtime != nil && issuedAt.Before(*account.PasswordMtime) {
return nil, fmt.Errorf("Account password has changed since token issued")
}
@@ -422,7 +490,7 @@ func (mgr *SessionManager) VerifyUsernamePassword(username string, password stri
// We choose how to verify based on the issuer.
func (mgr *SessionManager) VerifyToken(tokenString string) (jwt.Claims, error) {
parser := &jwt.Parser{
SkipClaimsValidation: true,
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation(), jwt.WithoutAudienceValidation()),
}
var claims jwt.StandardClaims
_, _, err := parser.ParseUnverified(tokenString, &claims)
@@ -439,7 +507,16 @@ func (mgr *SessionManager) VerifyToken(tokenString string) (jwt.Claims, error) {
if err != nil {
return claims, err
}
idToken, err := prov.Verify(claims.Audience, tokenString)
// Token must be verified for at least one audience
// TODO(jannfis): Is this the right way? Shouldn't we know our audience and only validate for the correct one?
var idToken *oidc.IDToken
for _, aud := range claims.Audience {
idToken, err = prov.Verify(aud, tokenString)
if err == nil {
break
}
}
if err != nil {
return claims, err
}
@@ -474,11 +551,11 @@ func Username(ctx context.Context) string {
if !ok {
return ""
}
switch jwtutil.GetField(mapClaims, "iss") {
switch jwtutil.StringField(mapClaims, "iss") {
case SessionManagerClaimsIssuer:
return jwtutil.GetField(mapClaims, "sub")
return jwtutil.StringField(mapClaims, "sub")
default:
return jwtutil.GetField(mapClaims, "email")
return jwtutil.StringField(mapClaims, "email")
}
}
@@ -487,7 +564,7 @@ func Iss(ctx context.Context) string {
if !ok {
return ""
}
return jwtutil.GetField(mapClaims, "iss")
return jwtutil.StringField(mapClaims, "iss")
}
func Iat(ctx context.Context) (time.Time, error) {
@@ -495,16 +572,7 @@ func Iat(ctx context.Context) (time.Time, error) {
if !ok {
return time.Time{}, errors.New("unable to extract token claims")
}
iatField, ok := mapClaims["iat"]
if !ok {
return time.Time{}, errors.New("token does not have iat claim")
}
if iat, ok := iatField.(float64); !ok {
return time.Time{}, errors.New("iat token field has unexpected type")
} else {
return time.Unix(int64(iat), 0), nil
}
return jwtutil.IssuedAtTime(mapClaims)
}
func Sub(ctx context.Context) string {
@@ -512,7 +580,7 @@ func Sub(ctx context.Context) string {
if !ok {
return ""
}
return jwtutil.GetField(mapClaims, "sub")
return jwtutil.StringField(mapClaims, "sub")
}
func Groups(ctx context.Context, scopes []string) []string {

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