feat: Applications in any namespace (#9755)

* feat: Applications in any namespace

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

* Fix typo in CI

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

* Create argocd-e2e-external namespace

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

* Update from codegen

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

* Remove debug code

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

* Update help text for -N option to app create

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

* Wrap error when retrieving AppProject from cache

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

* Check for controller namespace first before matching on additional ns

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

* Improve TestAppProjectIsSourceNamespacePermitted unit test

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

* Get rid of some debug leftovers

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

* Better error wrapping; return IsNotFound as-is

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

* Updates from codegen

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

* We don't need AppShortName() anymore

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

* Update end-to-end tests to use annotation methods

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

* Add e2e tests to test for app creation in not permitted ns

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

* Remove deprecated code

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

* Remove dead code

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

* Add RBACName() method to application type

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

* Update from codegen

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

* Fix e2e test

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

* Update codegen

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

* Move RBAC name generation to an application receiver

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

* Fix sync window status in UI

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

* Fix pod logs viewer

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

* Fix application events in UI

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

* Fix application search in UI

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

* Fix yarn lint

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

* Only set up cluster-wide application informer when additional namespaces are specified

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

* Adapt e2e test to a changed error message

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

* Application namespace should be taken into account for create

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

* Use non-qualified application name as Helm release name

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

* Support --app-namespace in e2e tests

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

* Enable more e2e tests

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

* Increase e2e timeout for newly added tests

Signed-off-by: jannfis <jann@mistrust.net>
This commit is contained in:
jannfis 2022-08-10 11:39:10 +02:00 committed by GitHub
parent 3d06d8202e
commit 068048cb80
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
100 changed files with 5895 additions and 1129 deletions

View file

@ -354,6 +354,7 @@ jobs:
ARGOCD_E2E_K3S: "true"
ARGOCD_IN_CI: "true"
ARGOCD_E2E_APISERVER_PORT: "8088"
ARGOCD_APPLICATION_NAMESPACES: "argocd-e2e-external"
ARGOCD_SERVER: "127.0.0.1:8088"
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}

View file

@ -47,7 +47,7 @@ ARGOCD_E2E_DEX_PORT?=5556
ARGOCD_E2E_YARN_HOST?=localhost
ARGOCD_E2E_DISABLE_AUTH?=
ARGOCD_E2E_TEST_TIMEOUT?=30m
ARGOCD_E2E_TEST_TIMEOUT?=45m
ARGOCD_IN_CI?=false
ARGOCD_TEST_E2E?=true
@ -81,6 +81,7 @@ define run-in-test-server
-e ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} \
-e ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} \
-e ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} \
-e ARGOCD_APPLICATION_NAMESPACES \
-e GITHUB_TOKEN \
-v ${DOCKER_SRC_MOUNT} \
-v ${GOPATH}/pkg/mod:/go/pkg/mod${VOLUME_MOUNT} \
@ -118,7 +119,7 @@ endef
#
define exec-in-test-server
docker exec -it -u $(shell id -u):$(shell id -g) -e ARGOCD_E2E_K3S=$(ARGOCD_E2E_K3S) argocd-test-server $(1)
docker exec -it -u $(shell id -u):$(shell id -g) -e ARGOCD_E2E_RECORD=$(ARGOCD_E2E_RECORD) -e ARGOCD_E2E_K3S=$(ARGOCD_E2E_K3S) argocd-test-server $(1)
endef
PATH:=$(PATH):$(PWD)/hack
@ -405,7 +406,7 @@ test-e2e:
test-e2e-local: cli-local
# NO_PROXY ensures all tests don't go out through a proxy if one is configured on the test system
export GO111MODULE=off
ARGOCD_GPG_ENABLED=true NO_PROXY=* ./hack/test.sh -timeout $(ARGOCD_E2E_TEST_TIMEOUT) -v ./test/e2e
ARGOCD_E2E_RECORD=${ARGOCD_E2E_RECORD} ARGOCD_GPG_ENABLED=true NO_PROXY=* ./hack/test.sh -timeout $(ARGOCD_E2E_TEST_TIMEOUT) -v ./test/e2e
# Spawns a shell in the test server container for debugging purposes
debug-test-server: test-tools-image
@ -426,6 +427,7 @@ start-e2e: test-tools-image
.PHONY: start-e2e-local
start-e2e-local: mod-vendor-local dep-ui-local cli-local
kubectl create ns argocd-e2e || true
kubectl create ns argocd-e2e-external || true
kubectl config set-context --current --namespace=argocd-e2e
kustomize build test/manifests/base | kubectl apply -f -
kubectl apply -f https://raw.githubusercontent.com/open-cluster-management/api/a6845f2ebcb186ec26b832f60c988537a58f3859/cluster/v1alpha1/0000_04_clusters.open-cluster-management.io_placementdecisions.crd.yaml
@ -446,6 +448,7 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
ARGOCD_ZJWT_FEATURE_FLAG=always \
ARGOCD_IN_CI=$(ARGOCD_IN_CI) \
BIN_MODE=$(ARGOCD_BIN_MODE) \
ARGOCD_APPLICATION_NAMESPACES=argocd-e2e-external \
ARGOCD_E2E_TEST=true \
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START}
@ -477,6 +480,7 @@ start-local: mod-vendor-local dep-ui-local cli-local
ARGOCD_IN_CI=false \
ARGOCD_GPG_ENABLED=$(ARGOCD_GPG_ENABLED) \
ARGOCD_E2E_TEST=false \
ARGOCD_APPLICATION_NAMESPACES=$(ARGOCD_APPLICATION_NAMESPACES) \
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START}
# Run goreman start with exclude option , provide exclude env variable with list of services

View file

@ -1,5 +1,5 @@
controller: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --otlp-address=${ARGOCD_OTLP_ADDRESS}"
api-server: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --otlp-address=${ARGOCD_OTLP_ADDRESS}"
controller: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''}"
api-server: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''}"
dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v2/cmd gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:$(grep "image: ghcr.io/dexidp/dex" manifests/base/dex/argocd-dex-server-deployment.yaml | cut -d':' -f3) dex serve /dex.yaml"
redis: bash -c "if [ \"$ARGOCD_REDIS_LOCAL\" == 'true' ]; then redis-server --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; else docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} redis:$(grep "image: redis" manifests/base/redis/argocd-redis-deployment.yaml | cut -d':' -f3) --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; fi"
repo-server: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-repo-server ARGOCD_GPG_ENABLED=${ARGOCD_GPG_ENABLED:-false} $COMMAND --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --otlp-address=${ARGOCD_OTLP_ADDRESS}"

View file

@ -265,6 +265,12 @@
"description": "the repoURL to restrict returned list applications.",
"name": "repo",
"in": "query"
},
{
"type": "string",
"description": "the application's namespace.",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -407,6 +413,11 @@
"type": "string",
"name": "kind",
"in": "query"
},
{
"type": "string",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -463,6 +474,11 @@
"type": "string",
"name": "kind",
"in": "query"
},
{
"type": "string",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -529,6 +545,12 @@
"description": "the repoURL to restrict returned list applications.",
"name": "repo",
"in": "query"
},
{
"type": "string",
"description": "the application's namespace.",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -568,6 +590,11 @@
"type": "string",
"name": "propagationPolicy",
"in": "query"
},
{
"type": "string",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -651,6 +678,11 @@
"type": "string",
"name": "resourceUID",
"in": "query"
},
{
"type": "string",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -758,6 +790,11 @@
"type": "boolean",
"name": "previous",
"in": "query"
},
{
"type": "string",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -803,6 +840,11 @@
"type": "string",
"name": "revision",
"in": "query"
},
{
"type": "string",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -834,6 +876,11 @@
"name": "name",
"in": "path",
"required": true
},
{
"type": "string",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -942,6 +989,11 @@
"type": "boolean",
"name": "previous",
"in": "query"
},
{
"type": "string",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -1007,6 +1059,11 @@
"type": "string",
"name": "kind",
"in": "query"
},
{
"type": "string",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -1074,6 +1131,11 @@
"type": "string",
"name": "patchType",
"in": "query"
},
{
"type": "string",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -1138,6 +1200,11 @@
"type": "boolean",
"name": "orphan",
"in": "query"
},
{
"type": "string",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -1194,6 +1261,11 @@
"type": "string",
"name": "kind",
"in": "query"
},
{
"type": "string",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -1256,6 +1328,11 @@
"type": "string",
"name": "kind",
"in": "query"
},
{
"type": "string",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -1295,6 +1372,12 @@
"name": "revision",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the application's namespace.",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -1378,6 +1461,11 @@
"type": "boolean",
"name": "validate",
"in": "query"
},
{
"type": "string",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -1448,6 +1536,11 @@
"name": "name",
"in": "path",
"required": true
},
{
"type": "string",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -3167,6 +3260,12 @@
"description": "the repoURL to restrict returned list applications.",
"name": "repo",
"in": "query"
},
{
"type": "string",
"description": "the application's namespace.",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -3232,6 +3331,11 @@
"type": "string",
"name": "kind",
"in": "query"
},
{
"type": "string",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
@ -3390,6 +3494,9 @@
"type": "object",
"title": "ApplicationPatchRequest is a request to patch an application",
"properties": {
"appNamespace": {
"type": "string"
},
"name": {
"type": "string"
},
@ -3415,6 +3522,9 @@
"applicationApplicationRollbackRequest": {
"type": "object",
"properties": {
"appNamespace": {
"type": "string"
},
"dryRun": {
"type": "boolean"
},
@ -3434,6 +3544,9 @@
"type": "object",
"title": "ApplicationSyncRequest is a request to apply the config state to live state",
"properties": {
"appNamespace": {
"type": "string"
},
"dryRun": {
"type": "boolean"
},
@ -3704,6 +3817,9 @@
"$ref": "#/definitions/v1alpha1ConfigManagementPlugin"
}
},
"controllerNamespace": {
"type": "string"
},
"dexConfig": {
"$ref": "#/definitions/clusterDexConfig"
},
@ -4755,6 +4871,13 @@
"$ref": "#/definitions/v1alpha1SignatureKey"
}
},
"sourceNamespaces": {
"type": "array",
"title": "SourceNamespaces defines the namespaces application resources are allowed to be created in",
"items": {
"type": "string"
}
},
"sourceRepos": {
"type": "array",
"title": "SourceRepos contains list of repository URLs which can be used for deployment",

View file

@ -60,6 +60,7 @@ func NewCommand() *cobra.Command {
repoServerPlaintext bool
repoServerStrictTLS bool
otlpAddress string
applicationNamespaces []string
)
var command = cobra.Command{
Use: cliName,
@ -148,7 +149,8 @@ func NewCommand() *cobra.Command {
metricsCacheExpiration,
metricsAplicationLabels,
kubectlParallelismLimit,
clusterFilter)
clusterFilter,
applicationNamespaces)
errors.CheckError(err)
cacheutil.CollectMetrics(redisClient, appController.GetMetricsServer())
@ -189,6 +191,7 @@ func NewCommand() *cobra.Command {
command.Flags().BoolVar(&repoServerStrictTLS, "repo-server-strict-tls", env.ParseBoolFromEnv("ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_STRICT_TLS", false), "Whether to use strict validation of the TLS cert presented by the repo server")
command.Flags().StringSliceVar(&metricsAplicationLabels, "metrics-application-labels", []string{}, "List of Application labels that will be added to the argocd_application_labels metric")
command.Flags().StringVar(&otlpAddress, "otlp-address", env.StringFromEnv("ARGOCD_APPLICATION_CONTROLLER_OTLP_ADDRESS", ""), "OpenTelemetry collector address to send traces to")
command.Flags().StringSliceVar(&applicationNamespaces, "application-namespaces", []string{}, "List of additional namespaces that applications are allowed to be created in")
cacheSrc = appstatecache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
redisClient = client
})

View file

@ -70,6 +70,7 @@ func NewCommand() *cobra.Command {
dexServerPlaintext bool
dexServerStrictTLS bool
staticAssetsDir string
applicationNamespaces []string
)
var command = &cobra.Command{
Use: cliName,
@ -182,6 +183,7 @@ func NewCommand() *cobra.Command {
ContentSecurityPolicy: contentSecurityPolicy,
RedisClient: redisClient,
StaticAssetsDir: staticAssetsDir,
ApplicationNamespaces: applicationNamespaces,
}
stats.RegisterStackDumper()
@ -232,6 +234,7 @@ func NewCommand() *cobra.Command {
command.Flags().BoolVar(&repoServerStrictTLS, "repo-server-strict-tls", env.ParseBoolFromEnv("ARGOCD_SERVER_REPO_SERVER_STRICT_TLS", false), "Perform strict validation of TLS certificates when connecting to repo server")
command.Flags().BoolVar(&dexServerPlaintext, "dex-server-plaintext", env.ParseBoolFromEnv("ARGOCD_SERVER_DEX_SERVER_PLAINTEXT", false), "Use a plaintext client (non-TLS) to connect to dex server")
command.Flags().BoolVar(&dexServerStrictTLS, "dex-server-strict-tls", env.ParseBoolFromEnv("ARGOCD_SERVER_DEX_SERVER_STRICT_TLS", false), "Perform strict validation of TLS certificates when connecting to dex server")
command.Flags().StringSliceVar(&applicationNamespaces, "application-namespaces", []string{}, "List of namespaces where application resources can exist")
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(command)
cacheSrc = servercache.AddCacheFlagsToCmd(command, func(client *redis.Client) {
redisClient = client

View file

@ -112,6 +112,7 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
labels []string
annotations []string
setFinalizer bool
appNamespace string
)
var command = &cobra.Command{
Use: "create APPNAME",
@ -146,6 +147,9 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
c.HelpFunc()(c, args)
os.Exit(1)
}
if appNamespace != "" {
app.Namespace = appNamespace
}
if setFinalizer {
app.Finalizers = append(app.Finalizers, "resources-finalizer.argocd.argoproj.io")
}
@ -190,6 +194,7 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
if err != nil {
log.Fatal(err)
}
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Namespace where the application will be created in")
cmdutil.AddAppFlags(command, &appOpts)
return command
}
@ -274,8 +279,13 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
acdClient := headless.NewClientOrDie(clientOpts, c)
conn, appIf := acdClient.NewApplicationClientOrDie()
defer argoio.Close(conn)
appName := args[0]
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName, Refresh: getRefreshType(refresh, hardRefresh)})
appName, appNs := argo.ParseAppQualifiedName(args[0], "")
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{
Name: &appName,
Refresh: getRefreshType(refresh, hardRefresh),
AppNamespace: &appNs,
})
errors.CheckError(err)
pConn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
@ -354,7 +364,7 @@ func NewApplicationLogsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
acdClient := headless.NewClientOrDie(clientOpts, c)
conn, appIf := acdClient.NewApplicationClientOrDie()
defer argoio.Close(conn)
appName := args[0]
appName, appNs := argo.ParseAppQualifiedName(args[0], "")
retry := true
for retry {
@ -372,6 +382,7 @@ func NewApplicationLogsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
Filter: &filter,
Container: pointer.String(container),
Previous: pointer.Bool(previous),
AppNamespace: &appNs,
})
if err != nil {
log.Fatalf("failed to get pod logs: %v", err)
@ -419,7 +430,7 @@ func NewApplicationLogsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
}
func printAppSummaryTable(app *argoappv1.Application, appURL string, windows *argoappv1.SyncWindows) {
fmt.Printf(printOpFmtStr, "Name:", app.Name)
fmt.Printf(printOpFmtStr, "Name:", app.QualifiedName())
fmt.Printf(printOpFmtStr, "Project:", app.Spec.GetProject())
fmt.Printf(printOpFmtStr, "Server:", getServer(app))
fmt.Printf(printOpFmtStr, "Namespace:", app.Spec.Destination.Namespace)
@ -590,11 +601,11 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
c.HelpFunc()(c, args)
os.Exit(1)
}
appName := args[0]
appName, appNs := argo.ParseAppQualifiedName(args[0], "")
argocdClient := headless.NewClientOrDie(clientOpts, c)
conn, appIf := argocdClient.NewApplicationClientOrDie()
defer argoio.Close(conn)
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName})
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName, AppNamespace: &appNs})
errors.CheckError(err)
visited := cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts)
if visited == 0 {
@ -604,9 +615,10 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
}
setParameterOverrides(app, appOpts.Parameters)
_, err = appIf.UpdateSpec(ctx, &applicationpkg.ApplicationUpdateSpecRequest{
Name: &app.Name,
Spec: &app.Spec,
Validate: &appOpts.Validate,
Name: &app.Name,
Spec: &app.Spec,
Validate: &appOpts.Validate,
AppNamespace: &appNs,
})
errors.CheckError(err)
},
@ -652,10 +664,10 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
c.HelpFunc()(c, args)
os.Exit(1)
}
appName := args[0]
appName, appNs := argo.ParseAppQualifiedName(args[0], "")
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
defer argoio.Close(conn)
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName})
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName, AppNamespace: &appNs})
errors.CheckError(err)
updated, nothingToUnset := unset(&app.Spec.Source, opts)
@ -669,9 +681,10 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts)
_, err = appIf.UpdateSpec(ctx, &applicationpkg.ApplicationUpdateSpecRequest{
Name: &app.Name,
Spec: &app.Spec,
Validate: &appOpts.Validate,
Name: &app.Name,
Spec: &app.Spec,
Validate: &appOpts.Validate,
AppNamespace: &appNs,
})
errors.CheckError(err)
},
@ -803,7 +816,6 @@ func getLocalObjects(ctx context.Context, app *argoappv1.Application, local, loc
func getLocalObjectsString(ctx context.Context, app *argoappv1.Application, local, localRepoRoot, appLabelKey, kubeVersion string, apiVersions []string, kustomizeOptions *argoappv1.KustomizeOptions,
configManagementPlugins []*argoappv1.ConfigManagementPlugin, trackingMethod string) []string {
res, err := repository.GenerateManifests(ctx, local, localRepoRoot, app.Spec.Source.TargetRevision, &repoapiclient.ManifestRequest{
Repo: &argoappv1.Repository{Repo: app.Spec.Source.RepoURL},
AppLabelKey: appLabelKey,
@ -882,10 +894,14 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
clientset := headless.NewClientOrDie(clientOpts, c)
conn, appIf := clientset.NewApplicationClientOrDie()
defer argoio.Close(conn)
appName := args[0]
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName, Refresh: getRefreshType(refresh, hardRefresh)})
appName, appNs := argo.ParseAppQualifiedName(args[0], "")
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{
Name: &appName,
Refresh: getRefreshType(refresh, hardRefresh),
AppNamespace: &appNs,
})
errors.CheckError(err)
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{ApplicationName: &appName})
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{ApplicationName: &appName, AppNamespace: &appNs})
errors.CheckError(err)
conn, settingsIf := clientset.NewSettingsClientOrDie()
defer argoio.Close(conn)
@ -894,8 +910,9 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
diffOption := &DifferenceOption{}
if revision != "" {
q := applicationpkg.ApplicationManifestQuery{
Name: &appName,
Revision: &revision,
Name: &appName,
Revision: &revision,
AppNamespace: &appNs,
}
res, err := appIf.GetManifests(ctx, &q)
errors.CheckError(err)
@ -943,7 +960,7 @@ func findandPrintDiff(ctx context.Context, app *argoappv1.Application, resources
items := make([]objKeyLiveTarget, 0)
if diffOptions.local != "" {
localObjs := groupObjsByKey(getLocalObjects(ctx, app, diffOptions.local, diffOptions.localRepoRoot, argoSettings.AppLabelKey, diffOptions.cluster.Info.ServerVersion, diffOptions.cluster.Info.APIVersions, argoSettings.KustomizeOptions, argoSettings.ConfigManagementPlugins, argoSettings.TrackingMethod), liveObjs, app.Spec.Destination.Namespace)
items = groupObjsForDiff(resources, localObjs, items, argoSettings, appName)
items = groupObjsForDiff(resources, localObjs, items, argoSettings, app.InstanceName(argoSettings.ControllerNamespace))
} else if diffOptions.revision != "" {
var unstructureds []*unstructured.Unstructured
for _, mfst := range diffOptions.res.Manifests {
@ -952,7 +969,7 @@ func findandPrintDiff(ctx context.Context, app *argoappv1.Application, resources
unstructureds = append(unstructureds, obj)
}
groupedObjs := groupObjsByKey(unstructureds, liveObjs, app.Spec.Destination.Namespace)
items = groupObjsForDiff(resources, groupedObjs, items, argoSettings, appName)
items = groupObjsForDiff(resources, groupedObjs, items, argoSettings, app.Name)
} else {
for i := range resources.Items {
res := resources.Items[i]
@ -1072,9 +1089,11 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
if promptFlag.Changed && promptFlag.Value.String() == "true" {
noPrompt = true
}
for _, appName := range args {
for _, appFullName := range args {
appName, appNs := argo.ParseAppQualifiedName(appFullName, "")
appDeleteReq := applicationpkg.ApplicationDeleteRequest{
Name: &appName,
Name: &appName,
AppNamespace: &appNs,
}
if c.Flag("cascade").Changed {
appDeleteReq.Cascade = &cascade
@ -1085,10 +1104,10 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
if cascade && isTerminal && !noPrompt {
var lowercaseAnswer string
if numOfApps == 1 {
lowercaseAnswer = cli.AskToProceedS("Are you sure you want to delete '" + appName + "' and all its resources? [y/n]")
lowercaseAnswer = cli.AskToProceedS("Are you sure you want to delete '" + appFullName + "' and all its resources? [y/n]")
} else {
if !isConfirmAll {
lowercaseAnswer = cli.AskToProceedS("Are you sure you want to delete '" + appName + "' and all its resources? [y/n/A] where 'A' is to delete all specified apps and their resources without prompting")
lowercaseAnswer = cli.AskToProceedS("Are you sure you want to delete '" + appFullName + "' and all its resources? [y/n/A] where 'A' is to delete all specified apps and their resources without prompting")
if lowercaseAnswer == "a" {
lowercaseAnswer = "y"
isConfirmAll = true
@ -1100,9 +1119,9 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
if lowercaseAnswer == "y" {
_, err := appIf.Delete(ctx, &appDeleteReq)
errors.CheckError(err)
fmt.Printf("application '%s' deleted\n", appName)
fmt.Printf("application '%s' deleted\n", appFullName)
} else {
fmt.Println("The command to delete '" + appName + "' was cancelled.")
fmt.Println("The command to delete '" + appFullName + "' was cancelled.")
}
} else {
_, err := appIf.Delete(ctx, &appDeleteReq)
@ -1120,7 +1139,7 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
// Print simple list of application names
func printApplicationNames(apps []argoappv1.Application) {
for _, app := range apps {
fmt.Println(app.Name)
fmt.Println(app.QualifiedName())
}
}
@ -1138,7 +1157,7 @@ func printApplicationTable(apps []argoappv1.Application, output *string) {
_, _ = fmt.Fprintf(w, fmtStr, headers...)
for _, app := range apps {
vals := []interface{}{
app.Name,
app.QualifiedName(),
getServer(&app),
app.Spec.Destination.Namespace,
app.Spec.GetProject(),
@ -1158,10 +1177,11 @@ func printApplicationTable(apps []argoappv1.Application, output *string) {
// NewApplicationListCommand returns a new instance of an `argocd app list` command
func NewApplicationListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
output string
selector string
projects []string
repo string
output string
selector string
projects []string
repo string
appNamespace string
)
var command = &cobra.Command{
Use: "list",
@ -1176,7 +1196,10 @@ func NewApplicationListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
defer argoio.Close(conn)
apps, err := appIf.List(ctx, &applicationpkg.ApplicationQuery{Selector: pointer.String(selector)})
apps, err := appIf.List(ctx, &applicationpkg.ApplicationQuery{
Selector: pointer.String(selector),
AppNamespace: &appNamespace,
})
errors.CheckError(err)
appList := apps.Items
if len(projects) != 0 {
@ -1202,6 +1225,7 @@ func NewApplicationListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
command.Flags().StringVarP(&selector, "selector", "l", "", "List apps by label")
command.Flags().StringArrayVarP(&projects, "project", "p", []string{}, "Filter by project name")
command.Flags().StringVarP(&repo, "repo", "r", "", "List apps by source repo URL")
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Only list applications in namespace")
return command
}
@ -1442,16 +1466,18 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
}
for _, i := range list.Items {
appNames = append(appNames, i.Name)
appNames = append(appNames, i.QualifiedName())
}
}
for _, appName := range appNames {
for _, appQualifiedName := range appNames {
appName, appNs := argo.ParseAppQualifiedName(appQualifiedName, "")
if len(selectedLabels) > 0 {
q := applicationpkg.ApplicationManifestQuery{
Name: &appName,
Revision: &revision,
Name: &appName,
AppNamespace: &appNs,
Revision: &revision,
}
res, err := appIf.GetManifests(ctx, &q)
@ -1484,7 +1510,10 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
var localObjsStrings []string
diffOption := &DifferenceOption{}
if local != "" {
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName})
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{
Name: &appName,
AppNamespace: &appNs,
})
errors.CheckError(err)
if app.Spec.SyncPolicy != nil && app.Spec.SyncPolicy.Automated != nil && !dryRun {
log.Fatal("Cannot use local sync when Automatic Sync Policy is enabled except with --dry-run")
@ -1527,14 +1556,15 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
}
syncReq := applicationpkg.ApplicationSyncRequest{
Name: &appName,
DryRun: &dryRun,
Revision: &revision,
Resources: selectedResources,
Prune: &prune,
Manifests: localObjsStrings,
Infos: getInfos(infos),
SyncOptions: syncOptionsFactory(),
Name: &appName,
AppNamespace: &appNs,
DryRun: &dryRun,
Revision: &revision,
Resources: selectedResources,
Prune: &prune,
Manifests: localObjsStrings,
Infos: getInfos(infos),
SyncOptions: syncOptionsFactory(),
}
switch strategy {
@ -1558,20 +1588,26 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
}
}
if diffChanges {
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName})
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{
Name: &appName,
AppNamespace: &appNs,
})
errors.CheckError(err)
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{ApplicationName: &appName})
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{
ApplicationName: &appName,
AppNamespace: &appNs,
})
errors.CheckError(err)
conn, settingsIf := acdClient.NewSettingsClientOrDie()
defer argoio.Close(conn)
argoSettings, err := settingsIf.Get(ctx, &settingspkg.SettingsQuery{})
errors.CheckError(err)
foundDiffs := false
fmt.Printf("====== Previewing differences between live and desired state of application %s ======\n", appName)
foundDiffs = findandPrintDiff(ctx, app, resources, argoSettings, appName, diffOption)
fmt.Printf("====== Previewing differences between live and desired state of application %s ======\n", appQualifiedName)
foundDiffs = findandPrintDiff(ctx, app, resources, argoSettings, appQualifiedName, diffOption)
if foundDiffs {
if !diffChangesConfirm {
yesno := cli.AskToProceed(fmt.Sprintf("Please review changes to application %s shown above. Do you want to continue the sync process? (y/n): ", appName))
yesno := cli.AskToProceed(fmt.Sprintf("Please review changes to application %s shown above. Do you want to continue the sync process? (y/n): ", appQualifiedName))
if !yesno {
os.Exit(0)
}
@ -1584,7 +1620,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
errors.CheckError(err)
if !async {
app, err := waitOnApplicationStatus(ctx, acdClient, appName, timeout, watchOpts{operation: true}, selectedResources)
app, err := waitOnApplicationStatus(ctx, acdClient, appQualifiedName, timeout, watchOpts{operation: true}, selectedResources)
errors.CheckError(err)
if !dryRun {
@ -1783,12 +1819,18 @@ func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client,
// time when the sync status lags behind when an operation completes
refresh := false
appRealName, appNs := argo.ParseAppQualifiedName(appName, "")
printFinalStatus := func(app *argoappv1.Application) *argoappv1.Application {
var err error
if refresh {
conn, appClient := acdClient.NewApplicationClientOrDie()
refreshType := string(argoappv1.RefreshTypeNormal)
app, err = appClient.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName, Refresh: &refreshType})
app, err = appClient.Get(ctx, &applicationpkg.ApplicationQuery{
Name: &appRealName,
Refresh: &refreshType,
AppNamespace: &appNs,
})
errors.CheckError(err)
_ = conn.Close()
}
@ -1821,7 +1863,10 @@ func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client,
prevStates := make(map[string]*resourceState)
conn, appClient := acdClient.NewApplicationClientOrDie()
defer argoio.Close(conn)
app, err := appClient.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName})
app, err := appClient.Get(ctx, &applicationpkg.ApplicationQuery{
Name: &appRealName,
AppNamespace: &appNs,
})
errors.CheckError(err)
appEventCh := acdClient.WatchApplicationWithRetry(ctx, appName, app.ResourceVersion)
for appEvent := range appEventCh {
@ -1965,8 +2010,11 @@ func NewApplicationHistoryCommand(clientOpts *argocdclient.ClientOptions) *cobra
}
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
defer argoio.Close(conn)
appName := args[0]
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName})
appName, appNs := argo.ParseAppQualifiedName(args[0], "")
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{
Name: &appName,
AppNamespace: &appNs,
})
errors.CheckError(err)
if output == "id" {
printApplicationHistoryIds(app.Status.History)
@ -2012,7 +2060,7 @@ func NewApplicationRollbackCommand(clientOpts *argocdclient.ClientOptions) *cobr
c.HelpFunc()(c, args)
os.Exit(1)
}
appName := args[0]
appName, appNs := argo.ParseAppQualifiedName(args[0], "")
var err error
depID := -1
if len(args) > 1 {
@ -2022,20 +2070,24 @@ func NewApplicationRollbackCommand(clientOpts *argocdclient.ClientOptions) *cobr
acdClient := headless.NewClientOrDie(clientOpts, c)
conn, appIf := acdClient.NewApplicationClientOrDie()
defer argoio.Close(conn)
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName})
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{
Name: &appName,
AppNamespace: &appNs,
})
errors.CheckError(err)
depInfo, err := findRevisionHistory(app, int64(depID))
errors.CheckError(err)
_, err = appIf.Rollback(ctx, &applicationpkg.ApplicationRollbackRequest{
Name: &appName,
Id: pointer.Int64(depInfo.ID),
Prune: pointer.Bool(prune),
Name: &appName,
AppNamespace: &appNs,
Id: pointer.Int64(depInfo.ID),
Prune: pointer.Bool(prune),
})
errors.CheckError(err)
_, err = waitOnApplicationStatus(ctx, acdClient, appName, timeout, watchOpts{
_, err = waitOnApplicationStatus(ctx, acdClient, app.QualifiedName(), timeout, watchOpts{
operation: true,
}, nil)
errors.CheckError(err)
@ -2090,11 +2142,14 @@ func NewApplicationManifestsCommand(clientOpts *argocdclient.ClientOptions) *cob
c.HelpFunc()(c, args)
os.Exit(1)
}
appName := args[0]
appName, appNs := argo.ParseAppQualifiedName(args[0], "")
clientset := headless.NewClientOrDie(clientOpts, c)
conn, appIf := clientset.NewApplicationClientOrDie()
defer argoio.Close(conn)
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{ApplicationName: &appName})
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{
ApplicationName: &appName,
AppNamespace: &appNs,
})
errors.CheckError(err)
var unstructureds []*unstructured.Unstructured
@ -2117,8 +2172,9 @@ func NewApplicationManifestsCommand(clientOpts *argocdclient.ClientOptions) *cob
unstructureds = getLocalObjects(context.Background(), app, local, localRepoRoot, argoSettings.AppLabelKey, cluster.ServerVersion, cluster.Info.APIVersions, argoSettings.KustomizeOptions, argoSettings.ConfigManagementPlugins, argoSettings.TrackingMethod)
} else if revision != "" {
q := applicationpkg.ApplicationManifestQuery{
Name: &appName,
Revision: pointer.String(revision),
Name: &appName,
AppNamespace: &appNs,
Revision: pointer.String(revision),
}
res, err := appIf.GetManifests(ctx, &q)
errors.CheckError(err)
@ -2167,10 +2223,13 @@ func NewApplicationTerminateOpCommand(clientOpts *argocdclient.ClientOptions) *c
c.HelpFunc()(c, args)
os.Exit(1)
}
appName := args[0]
appName, appNs := argo.ParseAppQualifiedName(args[0], "")
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
defer argoio.Close(conn)
_, err := appIf.TerminateOperation(ctx, &applicationpkg.OperationTerminateRequest{Name: &appName})
_, err := appIf.TerminateOperation(ctx, &applicationpkg.OperationTerminateRequest{
Name: &appName,
AppNamespace: &appNs,
})
errors.CheckError(err)
fmt.Printf("Application '%s' operation terminating\n", appName)
},
@ -2189,10 +2248,13 @@ func NewApplicationEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
c.HelpFunc()(c, args)
os.Exit(1)
}
appName := args[0]
appName, appNs := argo.ParseAppQualifiedName(args[0], "")
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
defer argoio.Close(conn)
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName})
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{
Name: &appName,
AppNamespace: &appNs,
})
errors.CheckError(err)
appData, err := json.Marshal(app.Spec)
errors.CheckError(err)
@ -2212,7 +2274,12 @@ func NewApplicationEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
var appOpts cmdutil.AppOptions
cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts)
_, err = appIf.UpdateSpec(ctx, &applicationpkg.ApplicationUpdateSpecRequest{Name: &app.Name, Spec: &updatedSpec, Validate: &appOpts.Validate})
_, err = appIf.UpdateSpec(ctx, &applicationpkg.ApplicationUpdateSpecRequest{
Name: &appName,
Spec: &updatedSpec,
Validate: &appOpts.Validate,
AppNamespace: &appNs,
})
if err != nil {
return fmt.Errorf("Failed to update application spec:\n%v", err)
}
@ -2243,14 +2310,15 @@ func NewApplicationPatchCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
c.HelpFunc()(c, args)
os.Exit(1)
}
appName := args[0]
appName, appNs := argo.ParseAppQualifiedName(args[0], "")
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
defer argoio.Close(conn)
patchedApp, err := appIf.Patch(ctx, &applicationpkg.ApplicationPatchRequest{
Name: &appName,
Patch: &patch,
PatchType: &patchType,
Name: &appName,
Patch: &patch,
PatchType: &patchType,
AppNamespace: &appNs,
})
errors.CheckError(err)

View file

@ -17,6 +17,7 @@ import (
"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
applicationpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
"github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/errors"
"github.com/argoproj/argo-cd/v2/util/io"
)
@ -62,10 +63,13 @@ func NewApplicationResourceActionsListCommand(clientOpts *argocdclient.ClientOpt
c.HelpFunc()(c, args)
os.Exit(1)
}
appName := args[0]
appName, appNs := argo.ParseAppQualifiedName(args[0], "")
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
defer io.Close(conn)
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{ApplicationName: &appName})
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{
ApplicationName: &appName,
AppNamespace: &appNs,
})
errors.CheckError(err)
filteredObjects, err := util.FilterResources(command.Flags().Changed("group"), resources.Items, group, kind, namespace, resourceName, true)
errors.CheckError(err)
@ -75,6 +79,7 @@ func NewApplicationResourceActionsListCommand(clientOpts *argocdclient.ClientOpt
gvk := obj.GroupVersionKind()
availActionsForResource, err := appIf.ListResourceActions(ctx, &applicationpkg.ApplicationResourceRequest{
Name: &appName,
AppNamespace: &appNs,
Namespace: pointer.String(obj.GetNamespace()),
ResourceName: pointer.String(obj.GetName()),
Group: pointer.String(gvk.Group),
@ -147,12 +152,15 @@ func NewApplicationResourceActionsRunCommand(clientOpts *argocdclient.ClientOpti
c.HelpFunc()(c, args)
os.Exit(1)
}
appName := args[0]
appName, appNs := argo.ParseAppQualifiedName(args[0], "")
actionName := args[1]
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
defer io.Close(conn)
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{ApplicationName: &appName})
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{
ApplicationName: &appName,
AppNamespace: &appNs,
})
errors.CheckError(err)
filteredObjects, err := util.FilterResources(command.Flags().Changed("group"), resources.Items, group, kind, namespace, resourceName, all)
errors.CheckError(err)
@ -169,6 +177,7 @@ func NewApplicationResourceActionsRunCommand(clientOpts *argocdclient.ClientOpti
objResourceName := obj.GetName()
_, err := appIf.RunResourceAction(ctx, &applicationpkg.ResourceActionRunRequest{
Name: &appName,
AppNamespace: &appNs,
Namespace: pointer.String(obj.GetNamespace()),
ResourceName: pointer.String(objResourceName),
Group: pointer.String(gvk.Group),

View file

@ -16,6 +16,7 @@ import (
"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
applicationpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
"github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/errors"
argoio "github.com/argoproj/argo-cd/v2/util/io"
@ -53,11 +54,14 @@ func NewApplicationPatchResourceCommand(clientOpts *argocdclient.ClientOptions)
c.HelpFunc()(c, args)
os.Exit(1)
}
appName := args[0]
appName, appNs := argo.ParseAppQualifiedName(args[0], "")
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
defer argoio.Close(conn)
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{ApplicationName: &appName})
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{
ApplicationName: &appName,
AppNamespace: &appNs,
})
errors.CheckError(err)
objectsToPatch, err := util.FilterResources(command.Flags().Changed("group"), resources.Items, group, kind, namespace, resourceName, all)
errors.CheckError(err)
@ -66,6 +70,7 @@ func NewApplicationPatchResourceCommand(clientOpts *argocdclient.ClientOptions)
gvk := obj.GroupVersionKind()
_, err = appIf.PatchResource(ctx, &applicationpkg.ApplicationResourcePatchRequest{
Name: &appName,
AppNamespace: &appNs,
Namespace: pointer.String(obj.GetNamespace()),
ResourceName: pointer.String(obj.GetName()),
Version: pointer.String(gvk.Version),
@ -111,11 +116,14 @@ func NewApplicationDeleteResourceCommand(clientOpts *argocdclient.ClientOptions)
c.HelpFunc()(c, args)
os.Exit(1)
}
appName := args[0]
appName, appNs := argo.ParseAppQualifiedName(args[0], "")
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
defer argoio.Close(conn)
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{ApplicationName: &appName})
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{
ApplicationName: &appName,
AppNamespace: &appNs,
})
errors.CheckError(err)
objectsToDelete, err := util.FilterResources(command.Flags().Changed("group"), resources.Items, group, kind, namespace, resourceName, all)
errors.CheckError(err)
@ -124,6 +132,7 @@ func NewApplicationDeleteResourceCommand(clientOpts *argocdclient.ClientOptions)
gvk := obj.GroupVersionKind()
_, err = appIf.DeleteResource(ctx, &applicationpkg.ApplicationResourceDeleteRequest{
Name: &appName,
AppNamespace: &appNs,
Namespace: pointer.String(obj.GetNamespace()),
ResourceName: pointer.String(obj.GetName()),
Version: pointer.String(gvk.Version),
@ -173,10 +182,13 @@ func NewApplicationListResourcesCommand(clientOpts *argocdclient.ClientOptions)
os.Exit(1)
}
listAll := !c.Flag("orphaned").Changed
appName := args[0]
appName, appNs := argo.ParseAppQualifiedName(args[0], "")
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
defer argoio.Close(conn)
appResourceTree, err := appIf.ResourceTree(ctx, &applicationpkg.ResourcesQuery{ApplicationName: &appName})
appResourceTree, err := appIf.ResourceTree(ctx, &applicationpkg.ResourcesQuery{
ApplicationName: &appName,
AppNamespace: &appNs,
})
errors.CheckError(err)
printResources(listAll, orphaned, appResourceTree)
},

View file

@ -552,7 +552,7 @@ func TestPrintAppSummaryTable(t *testing.T) {
return nil
})
expectation := `Name: test
expectation := `Name: argocd/test
Project: default
Server: local
Namespace: argocd

View file

@ -21,6 +21,7 @@ import (
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/config"
"github.com/argoproj/argo-cd/v2/util/errors"
"github.com/argoproj/argo-cd/v2/util/text/label"
@ -561,6 +562,7 @@ func constructAppsFromStdin() ([]*argoappv1.Application, error) {
func constructAppsBaseOnName(appName string, labels, annotations, args []string, appOpts AppOptions, flags *pflag.FlagSet) ([]*argoappv1.Application, error) {
var app *argoappv1.Application
// read arguments
if len(args) == 1 {
if appName != "" && appName != args[0] {
@ -568,13 +570,15 @@ func constructAppsBaseOnName(appName string, labels, annotations, args []string,
}
appName = args[0]
}
appName, appNs := argo.ParseAppQualifiedName(appName, "")
app = &argoappv1.Application{
TypeMeta: v1.TypeMeta{
Kind: application.ApplicationKind,
APIVersion: application.Group + "/v1alpha1",
},
ObjectMeta: v1.ObjectMeta{
Name: appName,
Name: appName,
Namespace: appNs,
},
}
SetAppSpecOptions(flags, &app.Spec, &appOpts)

View file

@ -20,10 +20,11 @@ import (
)
type ProjectOpts struct {
Description string
destinations []string
Sources []string
SignatureKeys []string
Description string
destinations []string
Sources []string
SignatureKeys []string
SourceNamespaces []string
orphanedResourcesEnabled bool
orphanedResourcesWarn bool
@ -45,6 +46,7 @@ func AddProjFlags(command *cobra.Command, opts *ProjectOpts) {
command.Flags().StringArrayVar(&opts.deniedClusterResources, "deny-cluster-resource", []string{}, "List of denied cluster level resources")
command.Flags().StringArrayVar(&opts.allowedNamespacedResources, "allow-namespaced-resource", []string{}, "List of allowed namespaced resources")
command.Flags().StringArrayVar(&opts.deniedNamespacedResources, "deny-namespaced-resource", []string{}, "List of denied namespaced resources")
command.Flags().StringSliceVar(&opts.SourceNamespaces, "source-namespaces", []string{}, "List of source namespaces for applications")
}
@ -104,6 +106,10 @@ func (opts *ProjectOpts) GetSignatureKeys() []v1alpha1.SignatureKey {
return signatureKeys
}
func (opts *ProjectOpts) GetSourceNamespaces() []string {
return opts.SourceNamespaces
}
func GetOrphanedResourcesSettings(flagSet *pflag.FlagSet, opts ProjectOpts) *v1alpha1.OrphanedResourcesMonitorSettings {
warnChanged := flagSet.Changed("orphaned-resources-warn")
if opts.orphanedResourcesEnabled || warnChanged {
@ -156,6 +162,8 @@ func SetProjSpecOptions(flags *pflag.FlagSet, spec *v1alpha1.AppProjectSpec, pro
spec.NamespaceResourceWhitelist = projOpts.GetAllowedNamespacedResources()
case "deny-namespaced-resource":
spec.NamespaceResourceBlacklist = projOpts.GetDeniedNamespacedResources()
case "source-namespaces":
spec.SourceNamespaces = projOpts.GetSourceNamespaces()
}
})
if flags.Changed("orphaned-resources") || flags.Changed("orphaned-resources-warn") {
@ -197,6 +205,5 @@ func ConstructAppProj(fileURL string, args []string, opts ProjectOpts, c *cobra.
proj.Name = args[0]
}
SetProjSpecOptions(c.Flags(), &proj.Spec, &opts)
return &proj, nil
}

View file

@ -113,6 +113,7 @@ type ApplicationController struct {
kubectlSemaphore *semaphore.Weighted
clusterFilter func(cluster *appv1.Cluster) bool
projByNameCache sync.Map
applicationNamespaces []string
}
// NewApplicationController creates new instance of ApplicationController.
@ -132,6 +133,7 @@ func NewApplicationController(
metricsApplicationLabels []string,
kubectlParallelismLimit int64,
clusterFilter func(cluster *appv1.Cluster) bool,
applicationNamespaces []string,
) (*ApplicationController, error) {
log.Infof("appResyncPeriod=%v, appHardResyncPeriod=%v", appResyncPeriod, appHardResyncPeriod)
db := db.NewDB(namespace, settingsMgr, kubeClientset)
@ -156,6 +158,7 @@ func NewApplicationController(
selfHealTimeout: selfHealTimeout,
clusterFilter: clusterFilter,
projByNameCache: sync.Map{},
applicationNamespaces: applicationNamespaces,
}
if kubectlParallelismLimit > 0 {
ctrl.kubectlSemaphore = semaphore.NewWeighted(kubectlParallelismLimit)
@ -270,6 +273,9 @@ type appProjCache struct {
appProj *appv1.AppProject
}
// GetAppProject gets an AppProject from the cache. If the AppProject is not
// yet cached, retrieves the AppProject from the K8s control plane and stores
// in the cache.
func (projCache *appProjCache) GetAppProject(ctx context.Context) (*appv1.AppProject, error) {
projCache.lock.Lock()
defer projCache.lock.Unlock()
@ -284,9 +290,21 @@ func (projCache *appProjCache) GetAppProject(ctx context.Context) (*appv1.AppPro
return projCache.appProj, nil
}
// getAppProj gets the AppProject for the given Application app.
func (ctrl *ApplicationController) getAppProj(app *appv1.Application) (*appv1.AppProject, error) {
projCache, _ := ctrl.projByNameCache.LoadOrStore(app.Spec.GetProject(), ctrl.newAppProjCache(app.Spec.GetProject()))
return projCache.(*appProjCache).GetAppProject(context.TODO())
proj, err := projCache.(*appProjCache).GetAppProject(context.TODO())
if err != nil {
if apierr.IsNotFound(err) {
return nil, err
} else {
return nil, fmt.Errorf("could not retrieve AppProject '%s' from cache: %v", app.Spec.Project, err)
}
}
if !proj.IsAppNamespacePermitted(app, ctrl.namespace) {
return nil, argo.ErrProjectNotPermitted(app.GetName(), app.GetNamespace(), proj.GetName())
}
return proj, nil
}
func (ctrl *ApplicationController) handleObjectUpdated(managedByApp map[string]bool, ref v1.ObjectReference) {
@ -300,13 +318,17 @@ func (ctrl *ApplicationController) handleObjectUpdated(managedByApp map[string]b
continue
}
managedByApp[app.Name] = true
managedByApp[app.InstanceName(ctrl.namespace)] = true
}
}
}
for appName, isManagedResource := range managedByApp {
obj, exists, err := ctrl.appInformer.GetIndexer().GetByKey(ctrl.namespace + "/" + appName)
if app, ok := obj.(*appv1.Application); exists && err == nil && ok && isSelfReferencedApp(app, ref) {
// The appName is given as <namespace>_<name>, but the indexer needs it
// format <namespace>/<name>
appKey := ctrl.toAppKey(appName)
obj, exists, err := ctrl.appInformer.GetIndexer().GetByKey(appKey)
app, ok := obj.(*appv1.Application)
if exists && err == nil && ok && isSelfReferencedApp(app, ref) {
// Don't force refresh app if related resource is application itself. This prevents infinite reconciliation loop.
continue
}
@ -316,6 +338,13 @@ func (ctrl *ApplicationController) handleObjectUpdated(managedByApp map[string]b
continue
}
// Enforce application's permission for the source namespace
_, err = ctrl.getAppProj(app)
if err != nil {
log.Errorf("Unable to determine project for app '%s': %v", app.QualifiedName(), err)
continue
}
level := ComparisonWithNothing
if isManagedResource {
level = CompareWithRecent
@ -330,10 +359,10 @@ func (ctrl *ApplicationController) handleObjectUpdated(managedByApp map[string]b
} else {
resKey = "(cluster-scoped)/" + ref.Name
}
log.Debugf("Refreshing app %s for change in cluster of object %s of type %s/%s", appName, resKey, ref.APIVersion, ref.Kind)
log.Debugf("Refreshing app %s for change in cluster of object %s of type %s/%s", appKey, resKey, ref.APIVersion, ref.Kind)
}
ctrl.requestAppRefresh(appName, &level, nil)
ctrl.requestAppRefresh(app.QualifiedName(), &level, nil)
}
}
@ -348,11 +377,11 @@ func (ctrl *ApplicationController) setAppManagedResources(a *appv1.Application,
if err != nil {
return nil, fmt.Errorf("error getting resource tree: %s", err)
}
err = ctrl.cache.SetAppResourcesTree(a.Name, tree)
err = ctrl.cache.SetAppResourcesTree(a.InstanceName(ctrl.namespace), tree)
if err != nil {
return nil, fmt.Errorf("error setting app resource tree: %s", err)
}
err = ctrl.cache.SetAppManagedResources(a.Name, managedResources)
err = ctrl.cache.SetAppManagedResources(a.InstanceName(ctrl.namespace), managedResources)
if err != nil {
return nil, fmt.Errorf("error setting app managed resources: %s", err)
}
@ -443,7 +472,8 @@ func (ctrl *ApplicationController) getResourceTree(a *appv1.Application, managed
err := ctrl.stateCache.IterateHierarchy(a.Spec.Destination.Server, k, func(child appv1.ResourceNode, appName string) bool {
belongToAnotherApp := false
if appName != "" {
if _, exists, err := ctrl.appInformer.GetIndexer().GetByKey(ctrl.namespace + "/" + appName); exists && err == nil {
appKey := ctrl.toAppKey(appName)
if _, exists, err := ctrl.appInformer.GetIndexer().GetByKey(appKey); exists && err == nil {
belongToAnotherApp = true
}
}
@ -474,7 +504,6 @@ func (ctrl *ApplicationController) getResourceTree(a *appv1.Application, managed
if err != nil {
return nil, err
}
return &appv1.ApplicationTree{Nodes: nodes, OrphanedNodes: orphanedNodes, Hosts: hosts}, nil
}
@ -705,15 +734,16 @@ func (ctrl *ApplicationController) Run(ctx context.Context, statusProcessors int
<-ctx.Done()
}
// requestAppRefresh adds a request for given app to the refresh queue. appName
// needs to be the qualified name of the application, i.e. <namespace>/<name>.
func (ctrl *ApplicationController) requestAppRefresh(appName string, compareWith *CompareWith, after *time.Duration) {
key := fmt.Sprintf("%s/%s", ctrl.namespace, appName)
key := ctrl.toAppKey(appName)
if compareWith != nil && after != nil {
ctrl.appComparisonTypeRefreshQueue.AddAfter(fmt.Sprintf("%s/%d", key, compareWith), *after)
} else {
if compareWith != nil {
ctrl.refreshRequestedAppsMutex.Lock()
ctrl.refreshRequestedApps[appName] = compareWith.Max(ctrl.refreshRequestedApps[appName])
ctrl.refreshRequestedApps[key] = compareWith.Max(ctrl.refreshRequestedApps[key])
ctrl.refreshRequestedAppsMutex.Unlock()
}
if after != nil {
@ -770,7 +800,7 @@ func (ctrl *ApplicationController) processAppOperationQueueItem() (processNext b
// If we get here, we are about process an operation but we cannot rely on informer since it might has stale data.
// So always retrieve the latest version to ensure it is not stale to avoid unnecessary syncing.
// We cannot rely on informer since applications might be updated by both application controller and api server.
freshApp, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace).Get(context.Background(), app.ObjectMeta.Name, metav1.GetOptions{})
freshApp, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.ObjectMeta.Namespace).Get(context.Background(), app.ObjectMeta.Name, metav1.GetOptions{})
if err != nil {
log.Errorf("Failed to retrieve latest application state: %v", err)
return
@ -816,7 +846,7 @@ func (ctrl *ApplicationController) processAppComparisonTypeQueueItem() (processN
log.Warnf("Unable to parse comparison type: %v", err)
return
} else {
ctrl.requestAppRefresh(parts[1], CompareWith(compareWith).Pointer(), nil)
ctrl.requestAppRefresh(ctrl.toAppQualifiedName(parts[1], parts[0]), CompareWith(compareWith).Pointer(), nil)
}
}
return
@ -910,7 +940,7 @@ func (ctrl *ApplicationController) getPermittedAppLiveObjects(app *appv1.Applica
}
func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Application) ([]*unstructured.Unstructured, error) {
logCtx := log.WithField("application", app.Name)
logCtx := log.WithField("application", app.QualifiedName())
logCtx.Infof("Deleting resources")
// Get refreshed application info, since informer app copy might be stale
app, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace).Get(context.Background(), app.Name, metav1.GetOptions{})
@ -1019,11 +1049,15 @@ func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Applic
logCtx.Infof("Resource entries removed from undefined cluster")
}
ctrl.projectRefreshQueue.Add(fmt.Sprintf("%s/%s", app.Namespace, app.Spec.GetProject()))
ctrl.projectRefreshQueue.Add(fmt.Sprintf("%s/%s", ctrl.namespace, app.Spec.GetProject()))
return objs, nil
}
func (ctrl *ApplicationController) removeCascadeFinalizer(app *appv1.Application) error {
_, err := ctrl.getAppProj(app)
if err != nil {
return err
}
app.UnSetCascadedDeletion()
var patch []byte
patch, _ = json.Marshal(map[string]interface{}{
@ -1032,7 +1066,7 @@ func (ctrl *ApplicationController) removeCascadeFinalizer(app *appv1.Application
},
})
_, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace).Patch(context.Background(), app.Name, types.MergePatchType, patch, metav1.PatchOptions{})
_, err = ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace).Patch(context.Background(), app.Name, types.MergePatchType, patch, metav1.PatchOptions{})
return err
}
@ -1061,7 +1095,7 @@ func (ctrl *ApplicationController) setAppCondition(app *appv1.Application, condi
}
func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Application) {
logCtx := log.WithField("application", app.Name)
logCtx := log.WithField("application", app.QualifiedName())
var state *appv1.OperationState
// Recover from any unexpected panics and automatically set the status to be failed
defer func() {
@ -1092,7 +1126,7 @@ func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Appli
retryAfter := time.Until(retryAt)
if retryAfter > 0 {
logCtx.Infof("Skipping retrying in-progress operation. Attempting again at: %s", retryAt.Format(time.RFC3339))
ctrl.requestAppRefresh(app.Name, CompareWithLatest.Pointer(), &retryAfter)
ctrl.requestAppRefresh(app.QualifiedName(), CompareWithLatest.Pointer(), &retryAfter)
return
} else {
// retrying operation. remove previous failure time in app since it is used as a trigger
@ -1118,11 +1152,24 @@ func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Appli
ctrl.appStateManager.SyncAppState(app, state)
}
// Check whether application is allowed to use project
_, err := ctrl.getAppProj(app)
if err != nil {
state.Phase = synccommon.OperationError
state.Message = err.Error()
}
if state.Phase == synccommon.OperationRunning {
// It's possible for an app to be terminated while we were operating on it. We do not want
// to clobber the Terminated state with Running. Get the latest app state to check for this.
freshApp, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace).Get(context.Background(), app.ObjectMeta.Name, metav1.GetOptions{})
freshApp, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace).Get(context.Background(), app.ObjectMeta.Name, metav1.GetOptions{})
if err == nil {
// App may have lost permissions to use the project meanwhile.
_, err = ctrl.getAppProj(freshApp)
if err != nil {
state.Phase = synccommon.OperationFailed
state.Message = fmt.Sprintf("operation not allowed: %v", err)
}
if freshApp.Status.OperationState != nil && freshApp.Status.OperationState.Phase == synccommon.OperationTerminating {
state.Phase = synccommon.OperationTerminating
state.Message = "operation is terminating"
@ -1155,7 +1202,7 @@ func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Appli
// sync/health information
if _, err := cache.MetaNamespaceKeyFunc(app); err == nil {
// force app refresh with using CompareWithLatest comparison type and trigger app reconciliation loop
ctrl.requestAppRefresh(app.Name, CompareWithLatest.Pointer(), nil)
ctrl.requestAppRefresh(app.QualifiedName(), CompareWithLatest.Pointer(), nil)
} else {
logCtx.Warnf("Fails to requeue application: %v", err)
}
@ -1183,7 +1230,7 @@ func (ctrl *ApplicationController) setOperationState(app *appv1.Application, sta
patch["operation"] = nil
}
if reflect.DeepEqual(app.Status.OperationState, state) {
log.Infof("No operation updates necessary to '%s'. Skipping patch", app.Name)
log.Infof("No operation updates necessary to '%s'. Skipping patch", app.QualifiedName())
return nil
}
patchJSON, err := json.Marshal(patch)
@ -1197,7 +1244,7 @@ func (ctrl *ApplicationController) setOperationState(app *appv1.Application, sta
}
}
appClient := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace)
appClient := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace)
_, err = appClient.Patch(context.Background(), app.Name, types.MergePatchType, patchJSON, metav1.PatchOptions{})
if err != nil {
// Stop retrying updating deleted application
@ -1206,7 +1253,7 @@ func (ctrl *ApplicationController) setOperationState(app *appv1.Application, sta
}
return err
}
log.Infof("updated '%s' operation (phase: %s)", app.Name, state.Phase)
log.Infof("updated '%s' operation (phase: %s)", app.QualifiedName(), state.Phase)
if state.Phase.Completed() {
eventInfo := argo.EventInfo{Reason: argo.EventReasonOperationCompleted}
var messages []string
@ -1268,7 +1315,7 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
}
app := origApp.DeepCopy()
logCtx := log.WithFields(log.Fields{"application": app.Name})
logCtx := log.WithFields(log.Fields{"application": app.QualifiedName()})
startTime := time.Now()
defer func() {
reconcileDuration := time.Since(startTime)
@ -1284,13 +1331,13 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
if comparisonLevel == ComparisonWithNothing {
managedResources := make([]*appv1.ResourceDiff, 0)
if err := ctrl.cache.GetAppManagedResources(app.Name, &managedResources); err != nil {
if err := ctrl.cache.GetAppManagedResources(app.InstanceName(ctrl.namespace), &managedResources); err != nil {
logCtx.Warnf("Failed to get cached managed resources for tree reconciliation, fall back to full reconciliation")
} else {
var tree *appv1.ApplicationTree
if tree, err = ctrl.getResourceTree(app, managedResources); err == nil {
app.Status.Summary = tree.GetSummary()
if err := ctrl.cache.SetAppResourcesTree(app.Name, tree); err != nil {
if err := ctrl.cache.SetAppResourcesTree(app.InstanceName(ctrl.namespace), tree); err != nil {
logCtx.Errorf("Failed to cache resources tree: %v", err)
return
}
@ -1307,10 +1354,10 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
app.Status.Health.Status = health.HealthStatusUnknown
ctrl.persistAppStatus(origApp, &app.Status)
if err := ctrl.cache.SetAppResourcesTree(app.Name, &appv1.ApplicationTree{}); err != nil {
if err := ctrl.cache.SetAppResourcesTree(app.InstanceName(ctrl.namespace), &appv1.ApplicationTree{}); err != nil {
log.Warnf("failed to set app resource tree: %v", err)
}
if err := ctrl.cache.SetAppManagedResources(app.Name, nil); err != nil {
if err := ctrl.cache.SetAppManagedResources(app.InstanceName(ctrl.namespace), nil); err != nil {
log.Warnf("failed to set app managed resources tree: %v", err)
}
return
@ -1383,7 +1430,7 @@ func resourceStatusKey(res appv1.ResourceStatus) string {
// Additionally returns whether full refresh was requested or not.
// If full refresh is requested then target and live state should be reconciled, else only live state tree should be updated.
func (ctrl *ApplicationController) needRefreshAppStatus(app *appv1.Application, statusRefreshTimeout, statusHardRefreshTimeout time.Duration) (bool, appv1.RefreshType, CompareWith) {
logCtx := log.WithFields(log.Fields{"application": app.Name})
logCtx := log.WithFields(log.Fields{"application": app.QualifiedName()})
var reason string
compareWith := CompareWithLatest
refreshType := appv1.RefreshTypeNormal
@ -1413,7 +1460,7 @@ func (ctrl *ApplicationController) needRefreshAppStatus(app *appv1.Application,
}
} else if !app.Spec.Destination.Equals(app.Status.Sync.ComparedTo.Destination) {
reason = "spec.destination differs"
} else if requested, level := ctrl.isRefreshRequested(app.Name); requested {
} else if requested, level := ctrl.isRefreshRequested(app.QualifiedName()); requested {
compareWith = level
reason = "controller refresh requested"
}
@ -1460,7 +1507,7 @@ func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application)
// normalizeApplication normalizes an application.spec and additionally persists updates if it changed
func (ctrl *ApplicationController) normalizeApplication(orig, app *appv1.Application) {
logCtx := log.WithFields(log.Fields{"application": app.Name})
logCtx := log.WithFields(log.Fields{"application": app.QualifiedName()})
app.Spec = *argo.NormalizeApplicationSpec(&app.Spec)
patch, modified, err := diff.CreateTwoWayMergePatch(orig, app, appv1.Application{})
if err != nil {
@ -1478,7 +1525,7 @@ func (ctrl *ApplicationController) normalizeApplication(orig, app *appv1.Applica
// persistAppStatus persists updates to application status. If no changes were made, it is a no-op
func (ctrl *ApplicationController) persistAppStatus(orig *appv1.Application, newStatus *appv1.ApplicationStatus) {
logCtx := log.WithFields(log.Fields{"application": orig.Name})
logCtx := log.WithFields(log.Fields{"application": orig.QualifiedName()})
if orig.Status.Sync.Status != newStatus.Sync.Status {
message := fmt.Sprintf("Updated sync status: %s -> %s", orig.Status.Sync.Status, newStatus.Sync.Status)
ctrl.auditLogger.LogAppEvent(orig, argo.EventInfo{Reason: argo.EventReasonResourceUpdated, Type: v1.EventTypeNormal}, message)
@ -1521,7 +1568,7 @@ func (ctrl *ApplicationController) autoSync(app *appv1.Application, syncStatus *
if app.Spec.SyncPolicy == nil || app.Spec.SyncPolicy.Automated == nil {
return nil
}
logCtx := log.WithFields(log.Fields{"application": app.Name})
logCtx := log.WithFields(log.Fields{"application": app.QualifiedName()})
if app.Operation != nil {
logCtx.Infof("Skipping auto-sync: another operation is in progress")
return nil
@ -1592,7 +1639,7 @@ func (ctrl *ApplicationController) autoSync(app *appv1.Application, syncStatus *
}
} else {
logCtx.Infof("Skipping auto-sync: already attempted sync to %s with timeout %v (retrying in %v)", desiredCommitSHA, ctrl.selfHealTimeout, retryAfter)
ctrl.requestAppRefresh(app.Name, CompareWithLatest.Pointer(), &retryAfter)
ctrl.requestAppRefresh(app.QualifiedName(), CompareWithLatest.Pointer(), &retryAfter)
return nil
}
@ -1669,10 +1716,22 @@ func (ctrl *ApplicationController) canProcessApp(obj interface{}) bool {
return ctrl.clusterFilter(cluster)
}
// Only process given app if it exists in a watched namespace, or in the
// control plane's namespace.
if app.Namespace != ctrl.namespace && !glob.MatchStringInList(ctrl.applicationNamespaces, app.Namespace, false) {
return false
}
return true
}
func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.SharedIndexInformer, applisters.ApplicationLister) {
watchNamespace := ctrl.namespace
// If we have at least one additional namespace configured, we need to
// watch on them all.
if len(ctrl.applicationNamespaces) > 0 {
watchNamespace = ""
}
refreshTimeout := ctrl.statusRefreshTimeout
if ctrl.statusHardRefreshTimeout.Seconds() != 0 && (ctrl.statusHardRefreshTimeout < ctrl.statusRefreshTimeout) {
refreshTimeout = ctrl.statusHardRefreshTimeout
@ -1680,10 +1739,23 @@ func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.Shar
informer := cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (apiruntime.Object, error) {
return ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace).List(context.TODO(), options)
// We are only interested in apps that exist in namespaces the
// user wants to be enabled.
appList, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(watchNamespace).List(context.TODO(), options)
if err != nil {
return nil, err
}
newItems := []appv1.Application{}
for _, app := range appList.Items {
if ctrl.namespace == app.Namespace || glob.MatchStringInList(ctrl.applicationNamespaces, app.Namespace, false) {
newItems = append(newItems, app)
}
}
appList.Items = newItems
return appList, nil
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
return ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace).Watch(context.TODO(), options)
return ctrl.applicationClientset.ArgoprojV1alpha1().Applications(watchNamespace).Watch(context.TODO(), options)
},
},
&appv1.Application{},
@ -1701,6 +1773,12 @@ func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.Shar
if err := argo.ValidateDestination(context.Background(), &app.Spec.Destination, ctrl.db); err != nil {
ctrl.setAppCondition(app, appv1.ApplicationCondition{Type: appv1.ApplicationConditionInvalidSpecError, Message: err.Error()})
}
// If the application is not allowed to use the project,
// log an error.
if _, err := ctrl.getAppProj(app); err != nil {
ctrl.setAppCondition(app, appv1.ApplicationCondition{Type: appv1.ApplicationConditionUnknownError, Message: err.Error()})
}
}
return cache.MetaNamespaceIndexFunc(obj)
@ -1748,10 +1826,10 @@ func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.Shar
oldApp, oldOK := old.(*appv1.Application)
newApp, newOK := new.(*appv1.Application)
if oldOK && newOK && automatedSyncEnabled(oldApp, newApp) {
log.WithField("application", newApp.Name).Info("Enabled automated sync")
log.WithField("application", newApp.QualifiedName()).Info("Enabled automated sync")
compareWith = CompareWithLatest.Pointer()
}
ctrl.requestAppRefresh(newApp.Name, compareWith, nil)
ctrl.requestAppRefresh(newApp.QualifiedName(), compareWith, nil)
ctrl.appOperationQueue.Add(key)
},
DeleteFunc: func(obj interface{}) {
@ -1771,7 +1849,7 @@ func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.Shar
}
func (ctrl *ApplicationController) RegisterClusterSecretUpdater(ctx context.Context) {
updater := NewClusterInfoUpdater(ctrl.stateCache, ctrl.db, ctrl.appLister.Applications(ctrl.namespace), ctrl.cache, ctrl.clusterFilter)
updater := NewClusterInfoUpdater(ctrl.stateCache, ctrl.db, ctrl.appLister.Applications(""), ctrl.cache, ctrl.clusterFilter, ctrl.getAppProj, ctrl.namespace)
go updater.Run(ctx)
}
@ -1804,3 +1882,21 @@ func automatedSyncEnabled(oldApp *appv1.Application, newApp *appv1.Application)
// nothing changed
return false
}
// toAppKey returns the application key from a given appName, that is, it will
// replace underscores with forward-slashes to become a <namespace>/<name>
// format. If the appName is an unqualified name (such as, "app"), it will use
// the controller's namespace in the key.
func (ctrl *ApplicationController) toAppKey(appName string) string {
if !strings.Contains(appName, "_") && !strings.Contains(appName, "/") {
return ctrl.namespace + "/" + appName
} else if strings.Contains(appName, "/") {
return appName
} else {
return strings.ReplaceAll(appName, "_", "/")
}
}
func (ctrl *ApplicationController) toAppQualifiedName(appName, appNamespace string) string {
return fmt.Sprintf("%s/%s", appNamespace, appName)
}

View file

@ -110,6 +110,7 @@ func newFakeController(data *fakeData) *ApplicationController {
[]string{},
0,
nil,
[]string{},
)
if err != nil {
panic(err)
@ -754,15 +755,17 @@ func TestHandleAppUpdated(t *testing.T) {
app := newFakeApp()
app.Spec.Destination.Namespace = test.FakeArgoCDNamespace
app.Spec.Destination.Server = argoappv1.KubernetesInternalAPIServerAddr
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}})
proj := defaultProj.DeepCopy()
proj.Spec.SourceNamespaces = []string{test.FakeArgoCDNamespace}
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app, proj}})
ctrl.handleObjectUpdated(map[string]bool{app.Name: true}, kube.GetObjectRef(kube.MustToUnstructured(app)))
isRequested, level := ctrl.isRefreshRequested(app.Name)
ctrl.handleObjectUpdated(map[string]bool{app.InstanceName(ctrl.namespace): true}, kube.GetObjectRef(kube.MustToUnstructured(app)))
isRequested, level := ctrl.isRefreshRequested(app.QualifiedName())
assert.False(t, isRequested)
assert.Equal(t, ComparisonWithNothing, level)
ctrl.handleObjectUpdated(map[string]bool{app.Name: true}, corev1.ObjectReference{UID: "test", Kind: kube.DeploymentKind, Name: "test", Namespace: "default"})
isRequested, level = ctrl.isRefreshRequested(app.Name)
ctrl.handleObjectUpdated(map[string]bool{app.InstanceName(ctrl.namespace): true}, corev1.ObjectReference{UID: "test", Kind: kube.DeploymentKind, Name: "test", Namespace: "default"})
isRequested, level = ctrl.isRefreshRequested(app.QualifiedName())
assert.True(t, isRequested)
assert.Equal(t, CompareWithRecent, level)
}
@ -785,11 +788,11 @@ func TestHandleOrphanedResourceUpdated(t *testing.T) {
ctrl.handleObjectUpdated(map[string]bool{}, corev1.ObjectReference{UID: "test", Kind: kube.DeploymentKind, Name: "test", Namespace: test.FakeArgoCDNamespace})
isRequested, level := ctrl.isRefreshRequested(app1.Name)
isRequested, level := ctrl.isRefreshRequested(app1.QualifiedName())
assert.True(t, isRequested)
assert.Equal(t, CompareWithRecent, level)
isRequested, level = ctrl.isRefreshRequested(app2.Name)
isRequested, level = ctrl.isRefreshRequested(app2.QualifiedName())
assert.True(t, isRequested)
assert.Equal(t, CompareWithRecent, level)
}
@ -1097,7 +1100,7 @@ func TestFinalizeProjectDeletion_DoesNotHaveApplications(t *testing.T) {
func TestProcessRequestedAppOperation_FailedNoRetries(t *testing.T) {
app := newFakeApp()
app.Spec.Project = "invalid-project"
app.Spec.Project = "default"
app.Operation = &argoappv1.Operation{
Sync: &argoappv1.SyncOperation{},
}
@ -1123,7 +1126,10 @@ func TestProcessRequestedAppOperation_InvalidDestination(t *testing.T) {
app.Operation = &argoappv1.Operation{
Sync: &argoappv1.SyncOperation{},
}
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}})
proj := defaultProj
proj.Name = "test-project"
proj.Spec.SourceNamespaces = []string{test.FakeArgoCDNamespace}
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app, &proj}})
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
receivedPatch := map[string]interface{}{}
func() {
@ -1311,3 +1317,22 @@ func TestMetricsExpiration(t *testing.T) {
ctrl = newFakeController(&fakeData{apps: []runtime.Object{app}, metricsCacheExpiration: 10 * time.Second})
assert.True(t, ctrl.metricsServer.HasExpiration())
}
func TestToAppKey(t *testing.T) {
ctrl := newFakeController(&fakeData{})
tests := []struct {
name string
input string
expected string
}{
{"From instance name", "foo_bar", "foo/bar"},
{"From qualified name", "foo/bar", "foo/bar"},
{"From unqualified name", "bar", ctrl.namespace + "/bar"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, ctrl.toAppKey(tt.input))
})
}
}

View file

@ -529,7 +529,7 @@ func (c *liveStateCache) GetManagedLiveObjs(a *appv1.Application, targetObjs []*
return nil, err
}
return clusterInfo.GetManagedLiveObjs(targetObjs, func(r *clustercache.Resource) bool {
return resInfo(r).AppName == a.Name
return resInfo(r).AppName == a.InstanceName(c.settingsMgr.GetNamespace())
})
}

View file

@ -28,6 +28,8 @@ type clusterInfoUpdater struct {
appLister v1alpha1.ApplicationNamespaceLister
cache *appstatecache.Cache
clusterFilter func(cluster *appv1.Cluster) bool
projGetter func(app *appv1.Application) (*appv1.AppProject, error)
namespace string
}
func NewClusterInfoUpdater(
@ -35,9 +37,11 @@ func NewClusterInfoUpdater(
db db.ArgoDB,
appLister v1alpha1.ApplicationNamespaceLister,
cache *appstatecache.Cache,
clusterFilter func(cluster *appv1.Cluster) bool) *clusterInfoUpdater {
clusterFilter func(cluster *appv1.Cluster) bool,
projGetter func(app *appv1.Application) (*appv1.AppProject, error),
namespace string) *clusterInfoUpdater {
return &clusterInfoUpdater{infoSource, db, appLister, cache, clusterFilter}
return &clusterInfoUpdater{infoSource, db, appLister, cache, clusterFilter, projGetter, namespace}
}
func (c *clusterInfoUpdater) Run(ctx context.Context) {
@ -93,6 +97,12 @@ func (c *clusterInfoUpdater) updateClusterInfo(cluster appv1.Cluster, info *cach
}
var appCount int64
for _, a := range apps {
if c.projGetter != nil {
proj, err := c.projGetter(a)
if err != nil || !proj.IsAppNamespacePermitted(a, c.namespace) {
continue
}
}
if err := argo.ValidateDestination(context.Background(), &a.Spec.Destination, c.db); err != nil {
continue
}

View file

@ -86,7 +86,7 @@ func TestClusterSecretUpdater(t *testing.T) {
}
lister := applisters.NewApplicationLister(appInformer.GetIndexer()).Applications(fakeNamespace)
updater := NewClusterInfoUpdater(nil, argoDB, lister, appCache, nil)
updater := NewClusterInfoUpdater(nil, argoDB, lister, appCache, nil, nil, fakeNamespace)
err = updater.updateClusterInfo(*cluster, info)
assert.NoError(t, err, "Invoking updateClusterInfo failed.")

View file

@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"reflect"
"strings"
"time"
"github.com/argoproj/gitops-engine/pkg/diff"
@ -177,7 +178,7 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1
NoCache: noCache,
NoRevisionCache: noRevisionCache,
AppLabelKey: appLabelKey,
AppName: app.Name,
AppName: app.InstanceName(m.namespace),
Namespace: app.Spec.Destination.Namespace,
ApplicationSource: &source,
Plugins: tools,
@ -200,7 +201,7 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1
}
ts.AddCheckpoint("unmarshal_ms")
logCtx := log.WithField("application", app.Name)
logCtx := log.WithField("application", app.QualifiedName())
for k, v := range ts.Timings() {
logCtx = logCtx.WithField(k, v.Milliseconds())
}
@ -349,7 +350,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
failedToLoadObjs := false
conditions := make([]v1alpha1.ApplicationCondition, 0)
logCtx := log.WithField("application", app.Name)
logCtx := log.WithField("application", app.QualifiedName())
logCtx.Infof("Comparing app state (cluster: %s, namespace: %s)", app.Spec.Destination.Server, app.Spec.Destination.Namespace)
var targetObjs []*unstructured.Unstructured
@ -427,10 +428,11 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
for _, liveObj := range liveObjByKey {
if liveObj != nil {
appInstanceName := m.resourceTracking.GetAppName(liveObj, appLabelKey, trackingMethod)
if appInstanceName != "" && appInstanceName != app.Name {
if appInstanceName != "" && appInstanceName != app.InstanceName(m.namespace) {
fqInstanceName := strings.ReplaceAll(appInstanceName, "_", "/")
conditions = append(conditions, v1alpha1.ApplicationCondition{
Type: v1alpha1.ApplicationConditionSharedResourceWarning,
Message: fmt.Sprintf("%s/%s is part of applications %s and %s", liveObj.GetKind(), liveObj.GetName(), app.Name, appInstanceName),
Message: fmt.Sprintf("%s/%s is part of applications %s and %s", liveObj.GetKind(), liveObj.GetName(), app.QualifiedName(), fqInstanceName),
LastTransitionTime: &now,
})
}
@ -644,7 +646,7 @@ func (m *appStateManager) persistRevisionHistory(app *v1alpha1.Application, revi
if err != nil {
return err
}
_, err = m.appclientset.ArgoprojV1alpha1().Applications(m.namespace).Patch(context.Background(), app.Name, types.MergePatchType, patch, metav1.PatchOptions{})
_, err = m.appclientset.ArgoprojV1alpha1().Applications(app.Namespace).Patch(context.Background(), app.Name, types.MergePatchType, patch, metav1.PatchOptions{})
return err
}

View file

@ -374,7 +374,7 @@ func TestSetManagedResourcesWithResourcesOfAnotherApp(t *testing.T) {
tree, err := ctrl.setAppManagedResources(app1, &comparisonResult{managedResources: make([]managedResource, 0)})
assert.NoError(t, err)
assert.Equal(t, len(tree.OrphanedNodes), 0)
assert.Equal(t, 0, len(tree.OrphanedNodes))
}
func TestReturnUnknownComparisonStateOnSettingLoadError(t *testing.T) {
@ -399,6 +399,7 @@ func TestReturnUnknownComparisonStateOnSettingLoadError(t *testing.T) {
func TestSetManagedResourcesKnownOrphanedResourceExceptions(t *testing.T) {
proj := defaultProj.DeepCopy()
proj.Spec.OrphanedResources = &argoappv1.OrphanedResourcesMonitorSettings{}
proj.Spec.SourceNamespaces = []string{"default"}
app := newFakeApp()
app.Namespace = "default"

View file

@ -109,7 +109,7 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
revision = syncOp.Revision
}
proj, err := argo.GetAppProject(&app.Spec, listersv1alpha1.NewAppProjectLister(m.projInformer.GetIndexer()), m.namespace, m.settingsMgr, m.db, context.TODO())
proj, err := argo.GetAppProject(app, listersv1alpha1.NewAppProjectLister(m.projInformer.GetIndexer()), m.namespace, m.settingsMgr, m.db, context.TODO())
if err != nil {
state.Phase = common.OperationError
state.Message = fmt.Sprintf("Failed to load application project: %v", err)
@ -157,7 +157,7 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
}
syncId := fmt.Sprintf("%05d-%s", syncIdPrefix, randSuffix)
logEntry := log.WithFields(log.Fields{"application": app.Name, "syncId": syncId})
logEntry := log.WithFields(log.Fields{"application": app.QualifiedName(), "syncId": syncId})
initialResourcesRes := make([]common.ResourceSyncResult, 0)
for i, res := range syncRes.Resources {
key := kube.ResourceKey{Group: res.Group, Kind: res.Kind, Namespace: res.Namespace, Name: res.Name}

View file

@ -16,6 +16,7 @@ argocd-application-controller [flags]
--app-hard-resync int Time period in seconds for application hard resync.
--app-resync int Time period in seconds for application resync. (default 180)
--app-state-cache-expiration duration Cache expiration for app state (default 1h0m0s)
--application-namespaces strings List of additional namespaces that applications are allowed to be created in
--as string Username to impersonate for the operation
--as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
--as-uid string UID to impersonate for the operation

View file

@ -14,6 +14,7 @@ argocd-server [flags]
```
--app-state-cache-expiration duration Cache expiration for app state (default 1h0m0s)
--application-namespaces strings List of namespaces where application resources can exist
--as string Username to impersonate for the operation
--as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
--as-uid string UID to impersonate for the operation

View file

@ -22,6 +22,7 @@ argocd admin proj generate-spec PROJECT [flags]
--orphaned-resources-warn Specifies if applications should have a warning condition when orphaned resources detected
-o, --output string Output format. One of: json|yaml (default "yaml")
--signature-keys strings GnuPG public key IDs for commit signature verification
--source-namespaces strings List of source namespaces for applications
-s, --src stringArray Permitted source repository URL
```

View file

@ -33,6 +33,7 @@ argocd app create APPNAME [flags]
```
--allow-empty Set allow zero live resources when sync is automated
--annotations stringArray Set metadata annotations (e.g. example=value)
-N, --app-namespace string Namespace where the application will be created in
--auto-prune Set automatic pruning when sync is automated
--config-management-plugin string Config management plugin name
--dest-name string K8s cluster Name (e.g. minikube)

View file

@ -19,11 +19,12 @@ argocd app list [flags]
### Options
```
-h, --help help for list
-o, --output string Output format. One of: wide|name|json|yaml (default "wide")
-p, --project stringArray Filter by project name
-r, --repo string List apps by source repo URL
-l, --selector string List apps by label
-N, --app-namespace string Only list applications in namespace
-h, --help help for list
-o, --output string Output format. One of: wide|name|json|yaml (default "wide")
-p, --project stringArray Filter by project name
-r, --repo string List apps by source repo URL
-l, --selector string List apps by label
```
### Options inherited from parent commands

View file

@ -20,6 +20,7 @@ argocd proj create PROJECT [flags]
--orphaned-resources Enables orphaned resources monitoring
--orphaned-resources-warn Specifies if applications should have a warning condition when orphaned resources detected
--signature-keys strings GnuPG public key IDs for commit signature verification
--source-namespaces strings List of source namespaces for applications
-s, --src stringArray Permitted source repository URL
--upsert Allows to override a project with the same name even if supplied project spec is different from existing spec
```

View file

@ -19,6 +19,7 @@ argocd proj set PROJECT [flags]
--orphaned-resources Enables orphaned resources monitoring
--orphaned-resources-warn Specifies if applications should have a warning condition when orphaned resources detected
--signature-keys strings GnuPG public key IDs for commit signature verification
--source-namespaces strings List of source namespaces for applications
-s, --src stringArray Permitted source repository URL
```

View file

@ -28,3 +28,11 @@ rules:
- pods/log
verbs:
- get # supports viewing pod logs from UI
- apiGroups:
- "argoproj.io"
resources:
- "applications"
verbs:
- get
- list
- watch

View file

@ -9121,6 +9121,12 @@ spec:
- keyID
type: object
type: array
sourceNamespaces:
description: SourceNamespaces defines the namespaces application resources
are allowed to be created in
items:
type: string
type: array
sourceRepos:
description: SourceRepos contains list of repository URLs which can
be used for deployment

View file

@ -221,6 +221,12 @@ spec:
- keyID
type: object
type: array
sourceNamespaces:
description: SourceNamespaces defines the namespaces application resources
are allowed to be created in
items:
type: string
type: array
sourceRepos:
description: SourceRepos contains list of repository URLs which can
be used for deployment

View file

@ -9121,6 +9121,12 @@ spec:
- keyID
type: object
type: array
sourceNamespaces:
description: SourceNamespaces defines the namespaces application resources
are allowed to be created in
items:
type: string
type: array
sourceRepos:
description: SourceRepos contains list of repository URLs which can
be used for deployment
@ -9573,6 +9579,14 @@ rules:
- pods/log
verbs:
- get
- apiGroups:
- argoproj.io
resources:
- applications
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding

View file

@ -9121,6 +9121,12 @@ spec:
- keyID
type: object
type: array
sourceNamespaces:
description: SourceNamespaces defines the namespaces application resources
are allowed to be created in
items:
type: string
type: array
sourceRepos:
description: SourceRepos contains list of repository URLs which can
be used for deployment
@ -9532,6 +9538,14 @@ rules:
- pods/log
verbs:
- get
- apiGroups:
- argoproj.io
resources:
- applications
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding

View file

@ -44,6 +44,7 @@ import (
versionpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/version"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/env"
grpc_util "github.com/argoproj/argo-cd/v2/util/grpc"
http_util "github.com/argoproj/argo-cd/v2/util/http"
@ -765,13 +766,18 @@ func (c *client) NewAccountClientOrDie() (io.Closer, accountpkg.AccountServiceCl
func (c *client) WatchApplicationWithRetry(ctx context.Context, appName string, revision string) chan *argoappv1.ApplicationWatchEvent {
appEventsCh := make(chan *argoappv1.ApplicationWatchEvent)
cancelled := false
appName, appNs := argo.ParseAppQualifiedName(appName, "")
go func() {
defer close(appEventsCh)
for !cancelled {
conn, appIf, err := c.NewApplicationClient()
if err == nil {
var wc applicationpkg.ApplicationService_WatchClient
wc, err = appIf.Watch(ctx, &applicationpkg.ApplicationQuery{Name: &appName, ResourceVersion: &revision})
wc, err = appIf.Watch(ctx, &applicationpkg.ApplicationQuery{
Name: &appName,
AppNamespace: &appNs,
ResourceVersion: &revision,
})
if err == nil {
for {
var appEvent *v1alpha1.ApplicationWatchEvent

File diff suppressed because it is too large Load diff

View file

@ -293,6 +293,10 @@ func local_request_ApplicationService_Get_0(ctx context.Context, marshaler runti
}
var (
filter_ApplicationService_GetApplicationSyncWindows_0 = &utilities.DoubleArray{Encoding: map[string]int{"name": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
)
func request_ApplicationService_GetApplicationSyncWindows_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ApplicationSyncWindowsQuery
var metadata runtime.ServerMetadata
@ -315,6 +319,13 @@ func request_ApplicationService_GetApplicationSyncWindows_0(ctx context.Context,
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ApplicationService_GetApplicationSyncWindows_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetApplicationSyncWindows(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
@ -342,11 +353,22 @@ func local_request_ApplicationService_GetApplicationSyncWindows_0(ctx context.Co
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ApplicationService_GetApplicationSyncWindows_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GetApplicationSyncWindows(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_ApplicationService_RevisionMetadata_0 = &utilities.DoubleArray{Encoding: map[string]int{"name": 0, "revision": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}}
)
func request_ApplicationService_RevisionMetadata_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RevisionMetadataQuery
var metadata runtime.ServerMetadata
@ -380,6 +402,13 @@ func request_ApplicationService_RevisionMetadata_0(ctx context.Context, marshale
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "revision", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ApplicationService_RevisionMetadata_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.RevisionMetadata(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
@ -418,6 +447,13 @@ func local_request_ApplicationService_RevisionMetadata_0(ctx context.Context, ma
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "revision", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ApplicationService_RevisionMetadata_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.RevisionMetadata(ctx, &protoReq)
return msg, metadata, err
@ -1143,6 +1179,10 @@ func local_request_ApplicationService_Rollback_0(ctx context.Context, marshaler
}
var (
filter_ApplicationService_TerminateOperation_0 = &utilities.DoubleArray{Encoding: map[string]int{"name": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
)
func request_ApplicationService_TerminateOperation_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq OperationTerminateRequest
var metadata runtime.ServerMetadata
@ -1165,6 +1205,13 @@ func request_ApplicationService_TerminateOperation_0(ctx context.Context, marsha
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ApplicationService_TerminateOperation_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.TerminateOperation(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
@ -1192,6 +1239,13 @@ func local_request_ApplicationService_TerminateOperation_0(ctx context.Context,
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ApplicationService_TerminateOperation_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.TerminateOperation(ctx, &protoReq)
return msg, metadata, err

View file

@ -98,6 +98,7 @@ type Settings struct {
UiBannerPosition string `protobuf:"bytes,20,opt,name=uiBannerPosition,proto3" json:"uiBannerPosition,omitempty"`
StatusBadgeRootUrl string `protobuf:"bytes,21,opt,name=statusBadgeRootUrl,proto3" json:"statusBadgeRootUrl,omitempty"`
ExecEnabled bool `protobuf:"varint,22,opt,name=execEnabled,proto3" json:"execEnabled,omitempty"`
ControllerNamespace string `protobuf:"bytes,23,opt,name=controllerNamespace,proto3" json:"controllerNamespace,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -290,6 +291,13 @@ func (m *Settings) GetExecEnabled() bool {
return false
}
func (m *Settings) GetControllerNamespace() string {
if m != nil {
return m.ControllerNamespace
}
return ""
}
type GoogleAnalyticsConfig struct {
TrackingID string `protobuf:"bytes,1,opt,name=trackingID,proto3" json:"trackingID,omitempty"`
AnonymizeUsers bool `protobuf:"varint,2,opt,name=anonymizeUsers,proto3" json:"anonymizeUsers,omitempty"`
@ -667,77 +675,78 @@ func init() {
func init() { proto.RegisterFile("server/settings/settings.proto", fileDescriptor_a480d494da040caa) }
var fileDescriptor_a480d494da040caa = []byte{
// 1106 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0x5d, 0x6f, 0x23, 0x35,
0x17, 0xd6, 0x34, 0xdd, 0x36, 0x39, 0xd9, 0x36, 0xad, 0xb7, 0xdb, 0x77, 0xde, 0x68, 0x49, 0x43,
0x2e, 0x56, 0x01, 0xc1, 0x84, 0x66, 0x85, 0x40, 0x48, 0x2b, 0x20, 0xc9, 0x6a, 0x37, 0x6c, 0x4a,
0xcb, 0x6c, 0xbb, 0x17, 0x48, 0xa8, 0x72, 0x67, 0xcc, 0xd4, 0x64, 0x62, 0x8f, 0x6c, 0x4f, 0x68,
0xf6, 0x92, 0x3b, 0x6e, 0xb8, 0x81, 0x5f, 0xc3, 0x0f, 0x40, 0x5c, 0x22, 0x71, 0x5f, 0xa1, 0x88,
0x1f, 0x82, 0xec, 0xf9, 0xe8, 0x34, 0x09, 0x1f, 0xd2, 0xde, 0xd9, 0xcf, 0x73, 0xbe, 0x7c, 0x7c,
0x8e, 0x8f, 0xa1, 0x21, 0x89, 0x98, 0x12, 0xd1, 0x91, 0x44, 0x29, 0xca, 0x02, 0x99, 0x2f, 0x9c,
0x48, 0x70, 0xc5, 0xd1, 0xa6, 0x17, 0xc6, 0x52, 0x11, 0x51, 0xdf, 0x0b, 0x78, 0xc0, 0x0d, 0xd6,
0xd1, 0xab, 0x84, 0xae, 0x3f, 0x08, 0x38, 0x0f, 0x42, 0xd2, 0xc1, 0x11, 0xed, 0x60, 0xc6, 0xb8,
0xc2, 0x8a, 0x72, 0x96, 0x2a, 0xd7, 0x47, 0x01, 0x55, 0x97, 0xf1, 0x85, 0xe3, 0xf1, 0x49, 0x07,
0x0b, 0xa3, 0xfe, 0x8d, 0x59, 0xbc, 0xeb, 0xf9, 0x9d, 0x69, 0xb7, 0x13, 0x8d, 0x03, 0xad, 0x29,
0x3b, 0x38, 0x8a, 0x42, 0xea, 0x19, 0xdd, 0xce, 0xf4, 0x10, 0x87, 0xd1, 0x25, 0x3e, 0xec, 0x04,
0x84, 0x11, 0x81, 0x15, 0xf1, 0x53, 0x6b, 0x9f, 0xfc, 0x8b, 0xb5, 0xc5, 0x93, 0x70, 0xea, 0x7b,
0x1d, 0x2f, 0xc4, 0x74, 0x92, 0xc6, 0xd3, 0xaa, 0xc1, 0xd6, 0x8b, 0x94, 0xfd, 0x22, 0x26, 0x62,
0xd6, 0xfa, 0x05, 0xa0, 0x9c, 0x21, 0xe8, 0xff, 0x50, 0x8a, 0x45, 0x68, 0x5b, 0x4d, 0xab, 0x5d,
0xe9, 0x6d, 0xce, 0xaf, 0x0f, 0x4a, 0x67, 0xee, 0xc8, 0xd5, 0x18, 0x7a, 0x0f, 0x2a, 0x3e, 0xb9,
0xea, 0x73, 0xf6, 0x35, 0x0d, 0xec, 0xb5, 0xa6, 0xd5, 0xae, 0x76, 0x91, 0x93, 0x66, 0xc6, 0x19,
0x64, 0x8c, 0x7b, 0x23, 0x84, 0xfa, 0x00, 0xda, 0x7f, 0xaa, 0x52, 0x32, 0x2a, 0xf7, 0x72, 0x95,
0xe3, 0xe1, 0xa0, 0x9f, 0x50, 0xbd, 0xed, 0xf9, 0xf5, 0x01, 0xdc, 0xec, 0xdd, 0x82, 0x1a, 0x6a,
0x42, 0x15, 0x47, 0xd1, 0x08, 0x5f, 0x90, 0xf0, 0x39, 0x99, 0xd9, 0xeb, 0x3a, 0x32, 0xb7, 0x08,
0xa1, 0x97, 0xb0, 0x2b, 0x88, 0xe4, 0xb1, 0xf0, 0xc8, 0xf1, 0x94, 0x08, 0x41, 0x7d, 0x22, 0xed,
0x3b, 0xcd, 0x52, 0xbb, 0xda, 0x6d, 0xe7, 0xde, 0xb2, 0x13, 0x3a, 0xee, 0xa2, 0xe8, 0x13, 0xa6,
0xc4, 0xcc, 0x5d, 0x36, 0x81, 0x1c, 0x40, 0x52, 0x61, 0x15, 0xcb, 0x1e, 0xf6, 0x03, 0xf2, 0x84,
0xe1, 0x8b, 0x90, 0xf8, 0xf6, 0x46, 0xd3, 0x6a, 0x97, 0xdd, 0x15, 0x0c, 0x7a, 0x06, 0xb5, 0xa4,
0x12, 0x3e, 0x65, 0x38, 0x9c, 0x29, 0xea, 0x49, 0x7b, 0xd3, 0x9c, 0xb9, 0x91, 0x47, 0xf1, 0xf4,
0x36, 0x9f, 0x1e, 0x77, 0x51, 0x0d, 0xbd, 0x82, 0x9d, 0x71, 0x2c, 0x15, 0x9f, 0xd0, 0x57, 0xe4,
0x38, 0x32, 0xd5, 0x64, 0x97, 0x8d, 0xa9, 0xcf, 0x9d, 0x9b, 0x02, 0x70, 0xb2, 0x02, 0x30, 0x8b,
0x73, 0xcf, 0x77, 0xa6, 0x5d, 0x27, 0x1a, 0x07, 0x8e, 0x2e, 0x27, 0xa7, 0x50, 0x4e, 0x4e, 0x56,
0x4e, 0xce, 0xf3, 0x05, 0xab, 0xee, 0x92, 0x1f, 0xf4, 0x26, 0xac, 0x5f, 0x92, 0x30, 0xb2, 0x2b,
0xc6, 0xdf, 0x56, 0x1e, 0xfa, 0x33, 0x12, 0x46, 0xae, 0xa1, 0xd0, 0x5b, 0xb0, 0x19, 0x85, 0x71,
0x40, 0x99, 0xb4, 0xc1, 0xa4, 0xb9, 0x96, 0x4b, 0x9d, 0x18, 0xdc, 0xcd, 0x78, 0x9d, 0xc3, 0x58,
0x12, 0x31, 0xe2, 0x7a, 0x37, 0xa0, 0x32, 0xc9, 0x61, 0x35, 0xc9, 0xe1, 0x32, 0x83, 0x7e, 0xb0,
0xe0, 0x7f, 0x9e, 0xc9, 0xca, 0x11, 0x66, 0x38, 0x20, 0x13, 0xc2, 0xd4, 0x49, 0xea, 0xeb, 0xae,
0xf1, 0x75, 0xfa, 0x7a, 0x19, 0xe8, 0xaf, 0x34, 0xee, 0xfe, 0x9d, 0x53, 0xf4, 0x0e, 0xec, 0xe6,
0x29, 0x7a, 0x49, 0x84, 0x34, 0x77, 0xb1, 0xd5, 0x2c, 0xb5, 0x2b, 0xee, 0x32, 0x81, 0xea, 0x50,
0x8e, 0x69, 0x5f, 0xca, 0x33, 0x77, 0x64, 0x6f, 0x9b, 0x4a, 0xcd, 0xf7, 0xa8, 0x0d, 0xb5, 0x98,
0xf6, 0x30, 0x63, 0x44, 0xf4, 0x39, 0x53, 0x84, 0x29, 0xbb, 0x66, 0x44, 0x16, 0x61, 0x5d, 0xf2,
0x19, 0xa4, 0x0d, 0xed, 0x24, 0x25, 0x5f, 0x80, 0xb4, 0xad, 0x08, 0x4b, 0xf9, 0x2d, 0x17, 0xfe,
0x09, 0x56, 0x8a, 0x08, 0x66, 0xef, 0x26, 0xb6, 0x16, 0x60, 0xf4, 0x10, 0xb6, 0x95, 0xc0, 0xde,
0x98, 0xb2, 0xe0, 0x88, 0xa8, 0x4b, 0xee, 0xdb, 0xc8, 0x08, 0x2e, 0xa0, 0xfa, 0x9c, 0x99, 0x83,
0x13, 0x22, 0x26, 0x98, 0xe9, 0xf8, 0xee, 0x99, 0x7b, 0x5a, 0x26, 0xd0, 0xdb, 0xb0, 0x93, 0x83,
0x5c, 0x52, 0x9d, 0x62, 0x7b, 0xcf, 0xd8, 0x5d, 0xc2, 0x17, 0xda, 0xc8, 0xe5, 0x5c, 0x9d, 0x89,
0xd0, 0xbe, 0x6f, 0xa4, 0x57, 0x30, 0xfa, 0xf4, 0xe4, 0x8a, 0x78, 0x59, 0xbf, 0xed, 0x9b, 0x18,
0x8a, 0x50, 0xfd, 0x27, 0x0b, 0xf6, 0x57, 0xb7, 0x31, 0xda, 0x81, 0xd2, 0x98, 0xcc, 0x92, 0xf7,
0xcb, 0xd5, 0x4b, 0xe4, 0xc3, 0x9d, 0x29, 0x0e, 0x63, 0x92, 0x3e, 0x59, 0xaf, 0xd9, 0x40, 0x8b,
0x6e, 0xdd, 0xc4, 0xf8, 0x47, 0x6b, 0x1f, 0x5a, 0xad, 0x73, 0xb8, 0xbf, 0xb2, 0xbf, 0x51, 0x03,
0x20, 0xcb, 0xf6, 0x70, 0x90, 0xc6, 0x56, 0x40, 0xf4, 0x1d, 0x61, 0xc6, 0xd9, 0x4c, 0x97, 0xd2,
0x99, 0x24, 0x42, 0x9a, 0x58, 0xcb, 0xee, 0x02, 0xda, 0xfa, 0xd9, 0x82, 0x75, 0xdd, 0x86, 0xc8,
0x86, 0x4d, 0xef, 0x12, 0x9b, 0x3c, 0x26, 0xd6, 0xb2, 0xad, 0x2e, 0x40, 0xbd, 0x3c, 0x25, 0x57,
0xca, 0x18, 0xa9, 0xb8, 0xf9, 0x1e, 0x3d, 0x06, 0xb8, 0xa0, 0x0c, 0x8b, 0xd9, 0x99, 0x08, 0xa5,
0x5d, 0x32, 0xdd, 0xf4, 0xc6, 0xad, 0xfe, 0x76, 0x7a, 0x39, 0x9f, 0xbc, 0x8a, 0x05, 0x85, 0xfa,
0x63, 0xa8, 0x2d, 0xd0, 0x2b, 0xb2, 0xbd, 0x57, 0xcc, 0x76, 0xa5, 0x98, 0x9d, 0x07, 0xb0, 0x91,
0xf4, 0x14, 0x42, 0xb0, 0xce, 0xf0, 0x84, 0xa4, 0x6a, 0x66, 0xdd, 0xfa, 0x18, 0x2a, 0xf9, 0x08,
0x41, 0x5d, 0x00, 0x8f, 0x33, 0x46, 0x3c, 0xc5, 0x85, 0xb4, 0x2d, 0x13, 0xe8, 0xcd, 0xa8, 0xe9,
0x67, 0x94, 0x5b, 0x90, 0x6a, 0x3d, 0x82, 0x4a, 0x4e, 0xac, 0xf2, 0xa0, 0x31, 0x35, 0x8b, 0xb2,
0xc0, 0xcc, 0xba, 0xf5, 0x7d, 0x09, 0x0a, 0x63, 0x67, 0xa5, 0xda, 0x3e, 0x6c, 0x50, 0x29, 0x63,
0x22, 0x52, 0xc5, 0x74, 0x87, 0xda, 0x50, 0xf6, 0x42, 0x4a, 0x98, 0x1a, 0x0e, 0xcc, 0x64, 0xab,
0xf4, 0xee, 0xce, 0xaf, 0x0f, 0xca, 0xfd, 0x14, 0x73, 0x73, 0x16, 0x1d, 0x42, 0xd5, 0x0b, 0x69,
0x46, 0x24, 0x03, 0xac, 0x57, 0x9b, 0x5f, 0x1f, 0x54, 0xfb, 0xa3, 0x61, 0x2e, 0x5f, 0x94, 0xd1,
0x4e, 0xa5, 0xc7, 0xa3, 0x74, 0x8c, 0x55, 0xdc, 0x74, 0x87, 0xce, 0x61, 0x8b, 0xfa, 0xa7, 0x7c,
0x4c, 0x58, 0xdf, 0x8c, 0x74, 0x7b, 0xc3, 0xe4, 0xe6, 0xe1, 0x8a, 0x99, 0xea, 0x0c, 0x8b, 0x82,
0xe6, 0xba, 0x7a, 0xbb, 0xf3, 0xeb, 0x83, 0xad, 0xe1, 0xa0, 0x80, 0xbb, 0xb7, 0xed, 0xd5, 0x67,
0x80, 0x96, 0xf5, 0x56, 0x5c, 0xf3, 0xd1, 0xed, 0xa6, 0xfa, 0xe0, 0x1f, 0x9b, 0x2a, 0xf9, 0x93,
0x38, 0xf9, 0xa7, 0x4a, 0x0f, 0x77, 0xc7, 0xd8, 0x2f, 0xd4, 0x47, 0xf7, 0x2b, 0xa8, 0x65, 0x33,
0xfa, 0x05, 0x11, 0x53, 0xea, 0x11, 0xf4, 0x19, 0x94, 0x9e, 0x12, 0x85, 0xf6, 0x97, 0x86, 0xb8,
0xf9, 0xb8, 0xd4, 0x77, 0x97, 0xf0, 0x96, 0xfd, 0xdd, 0xef, 0x7f, 0xfe, 0xb8, 0x86, 0xd0, 0x8e,
0xf9, 0x8c, 0x4d, 0x0f, 0xf3, 0x8f, 0x50, 0xaf, 0xff, 0xeb, 0xbc, 0x61, 0xfd, 0x36, 0x6f, 0x58,
0x7f, 0xcc, 0x1b, 0xd6, 0x97, 0xef, 0xff, 0xb7, 0x4f, 0x59, 0x72, 0x87, 0xb9, 0x91, 0x8b, 0x0d,
0xf3, 0x85, 0x7a, 0xf4, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe2, 0xea, 0x37, 0xfe, 0x31, 0x0a,
0x00, 0x00,
// 1129 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0x4f, 0x6f, 0x1b, 0x45,
0x14, 0xd7, 0xd6, 0x69, 0x62, 0xbf, 0x34, 0x75, 0x32, 0x6d, 0xd3, 0xc5, 0x2a, 0x8e, 0xf1, 0xa1,
0x32, 0x08, 0xd6, 0x8d, 0x2b, 0x04, 0x42, 0xaa, 0x00, 0xdb, 0x55, 0x6b, 0xea, 0xb6, 0x61, 0xdb,
0xf4, 0x80, 0x84, 0xaa, 0xc9, 0xee, 0x63, 0xb3, 0x78, 0x3d, 0xb3, 0x9a, 0x99, 0x35, 0x75, 0x8f,
0xdc, 0xb8, 0x70, 0x81, 0x4f, 0xc3, 0x27, 0xe0, 0x88, 0xc4, 0x3d, 0x42, 0x16, 0x1f, 0x82, 0x23,
0x9a, 0xd9, 0x3f, 0xd9, 0xd8, 0xe6, 0x8f, 0xd4, 0xdb, 0xcc, 0xef, 0xf7, 0xfe, 0xcd, 0x9b, 0x37,
0xf3, 0x1e, 0x34, 0x25, 0x8a, 0x19, 0x8a, 0xae, 0x44, 0xa5, 0x42, 0x16, 0xc8, 0x62, 0xe1, 0xc4,
0x82, 0x2b, 0x4e, 0xb6, 0xbc, 0x28, 0x91, 0x0a, 0x45, 0xe3, 0x7a, 0xc0, 0x03, 0x6e, 0xb0, 0xae,
0x5e, 0xa5, 0x74, 0xe3, 0x56, 0xc0, 0x79, 0x10, 0x61, 0x97, 0xc6, 0x61, 0x97, 0x32, 0xc6, 0x15,
0x55, 0x21, 0x67, 0x99, 0x72, 0x63, 0x1c, 0x84, 0xea, 0x34, 0x39, 0x71, 0x3c, 0x3e, 0xed, 0x52,
0x61, 0xd4, 0xbf, 0x35, 0x8b, 0x0f, 0x3c, 0xbf, 0x3b, 0xeb, 0x75, 0xe3, 0x49, 0xa0, 0x35, 0x65,
0x97, 0xc6, 0x71, 0x14, 0x7a, 0x46, 0xb7, 0x3b, 0x3b, 0xa4, 0x51, 0x7c, 0x4a, 0x0f, 0xbb, 0x01,
0x32, 0x14, 0x54, 0xa1, 0x9f, 0x59, 0xfb, 0xec, 0x3f, 0xac, 0x2d, 0x9f, 0x84, 0x87, 0xbe, 0xd7,
0xf5, 0x22, 0x1a, 0x4e, 0xb3, 0x78, 0xda, 0x75, 0xd8, 0x79, 0x96, 0xb1, 0x5f, 0x26, 0x28, 0xe6,
0xed, 0xbf, 0x00, 0xaa, 0x39, 0x42, 0xde, 0x82, 0x4a, 0x22, 0x22, 0xdb, 0x6a, 0x59, 0x9d, 0x5a,
0x7f, 0x6b, 0x71, 0x76, 0x50, 0x39, 0x76, 0xc7, 0xae, 0xc6, 0xc8, 0x1d, 0xa8, 0xf9, 0xf8, 0x6a,
0xc0, 0xd9, 0x37, 0x61, 0x60, 0x5f, 0x6a, 0x59, 0x9d, 0xed, 0x1e, 0x71, 0xb2, 0xcc, 0x38, 0xc3,
0x9c, 0x71, 0xcf, 0x85, 0xc8, 0x00, 0x40, 0xfb, 0xcf, 0x54, 0x2a, 0x46, 0xe5, 0x5a, 0xa1, 0xf2,
0x74, 0x34, 0x1c, 0xa4, 0x54, 0xff, 0xea, 0xe2, 0xec, 0x00, 0xce, 0xf7, 0x6e, 0x49, 0x8d, 0xb4,
0x60, 0x9b, 0xc6, 0xf1, 0x98, 0x9e, 0x60, 0xf4, 0x08, 0xe7, 0xf6, 0x86, 0x8e, 0xcc, 0x2d, 0x43,
0xe4, 0x05, 0xec, 0x09, 0x94, 0x3c, 0x11, 0x1e, 0x3e, 0x9d, 0xa1, 0x10, 0xa1, 0x8f, 0xd2, 0xbe,
0xdc, 0xaa, 0x74, 0xb6, 0x7b, 0x9d, 0xc2, 0x5b, 0x7e, 0x42, 0xc7, 0x5d, 0x16, 0xbd, 0xcf, 0x94,
0x98, 0xbb, 0xab, 0x26, 0x88, 0x03, 0x44, 0x2a, 0xaa, 0x12, 0xd9, 0xa7, 0x7e, 0x80, 0xf7, 0x19,
0x3d, 0x89, 0xd0, 0xb7, 0x37, 0x5b, 0x56, 0xa7, 0xea, 0xae, 0x61, 0xc8, 0x43, 0xa8, 0xa7, 0x95,
0xf0, 0x39, 0xa3, 0xd1, 0x5c, 0x85, 0x9e, 0xb4, 0xb7, 0xcc, 0x99, 0x9b, 0x45, 0x14, 0x0f, 0x2e,
0xf2, 0xd9, 0x71, 0x97, 0xd5, 0xc8, 0x6b, 0xd8, 0x9d, 0x24, 0x52, 0xf1, 0x69, 0xf8, 0x1a, 0x9f,
0xc6, 0xa6, 0x9a, 0xec, 0xaa, 0x31, 0xf5, 0xc4, 0x39, 0x2f, 0x00, 0x27, 0x2f, 0x00, 0xb3, 0x78,
0xe9, 0xf9, 0xce, 0xac, 0xe7, 0xc4, 0x93, 0xc0, 0xd1, 0xe5, 0xe4, 0x94, 0xca, 0xc9, 0xc9, 0xcb,
0xc9, 0x79, 0xb4, 0x64, 0xd5, 0x5d, 0xf1, 0x43, 0xde, 0x81, 0x8d, 0x53, 0x8c, 0x62, 0xbb, 0x66,
0xfc, 0xed, 0x14, 0xa1, 0x3f, 0xc4, 0x28, 0x76, 0x0d, 0x45, 0xde, 0x85, 0xad, 0x38, 0x4a, 0x82,
0x90, 0x49, 0x1b, 0x4c, 0x9a, 0xeb, 0x85, 0xd4, 0x91, 0xc1, 0xdd, 0x9c, 0xd7, 0x39, 0x4c, 0x24,
0x8a, 0x31, 0xd7, 0xbb, 0x61, 0x28, 0xd3, 0x1c, 0x6e, 0xa7, 0x39, 0x5c, 0x65, 0xc8, 0x8f, 0x16,
0xdc, 0xf4, 0x4c, 0x56, 0x1e, 0x53, 0x46, 0x03, 0x9c, 0x22, 0x53, 0x47, 0x99, 0xaf, 0x2b, 0xc6,
0xd7, 0xf3, 0x37, 0xcb, 0xc0, 0x60, 0xad, 0x71, 0xf7, 0x9f, 0x9c, 0x92, 0xf7, 0x61, 0xaf, 0x48,
0xd1, 0x0b, 0x14, 0xd2, 0xdc, 0xc5, 0x4e, 0xab, 0xd2, 0xa9, 0xb9, 0xab, 0x04, 0x69, 0x40, 0x35,
0x09, 0x07, 0x52, 0x1e, 0xbb, 0x63, 0xfb, 0xaa, 0xa9, 0xd4, 0x62, 0x4f, 0x3a, 0x50, 0x4f, 0xc2,
0x3e, 0x65, 0x0c, 0xc5, 0x80, 0x33, 0x85, 0x4c, 0xd9, 0x75, 0x23, 0xb2, 0x0c, 0xeb, 0x92, 0xcf,
0x21, 0x6d, 0x68, 0x37, 0x2d, 0xf9, 0x12, 0xa4, 0x6d, 0xc5, 0x54, 0xca, 0xef, 0xb8, 0xf0, 0x8f,
0xa8, 0x52, 0x28, 0x98, 0xbd, 0x97, 0xda, 0x5a, 0x82, 0xc9, 0x6d, 0xb8, 0xaa, 0x04, 0xf5, 0x26,
0x21, 0x0b, 0x1e, 0xa3, 0x3a, 0xe5, 0xbe, 0x4d, 0x8c, 0xe0, 0x12, 0xaa, 0xcf, 0x99, 0x3b, 0x38,
0x42, 0x31, 0xa5, 0x4c, 0xc7, 0x77, 0xcd, 0xdc, 0xd3, 0x2a, 0x41, 0xde, 0x83, 0xdd, 0x02, 0xe4,
0x32, 0xd4, 0x29, 0xb6, 0xaf, 0x1b, 0xbb, 0x2b, 0xf8, 0xd2, 0x33, 0x72, 0x39, 0x57, 0xc7, 0x22,
0xb2, 0x6f, 0x18, 0xe9, 0x35, 0x8c, 0x3e, 0x3d, 0xbe, 0x42, 0x2f, 0x7f, 0x6f, 0xfb, 0x26, 0x86,
0x32, 0x44, 0xee, 0xc0, 0x35, 0x8f, 0x33, 0x25, 0x78, 0x14, 0xa1, 0x78, 0x42, 0xa7, 0x28, 0x63,
0xea, 0xa1, 0x7d, 0xd3, 0x98, 0x5c, 0x47, 0x35, 0x7e, 0xb6, 0x60, 0x7f, 0xfd, 0xc3, 0x27, 0xbb,
0x50, 0x99, 0xe0, 0x3c, 0xfd, 0xf1, 0x5c, 0xbd, 0x24, 0x3e, 0x5c, 0x9e, 0xd1, 0x28, 0xc1, 0xec,
0x93, 0x7b, 0xc3, 0x27, 0xb7, 0xec, 0xd6, 0x4d, 0x8d, 0x7f, 0x72, 0xe9, 0x63, 0xab, 0xfd, 0x12,
0x6e, 0xac, 0xfd, 0x11, 0x48, 0x13, 0x20, 0xbf, 0x9f, 0xd1, 0x30, 0x8b, 0xad, 0x84, 0xe8, 0x5b,
0xa5, 0x8c, 0xb3, 0xb9, 0x2e, 0xbe, 0x63, 0x89, 0x42, 0x9a, 0x58, 0xab, 0xee, 0x12, 0xda, 0xfe,
0xc5, 0x82, 0x0d, 0xfd, 0x70, 0x89, 0x0d, 0x5b, 0xde, 0x29, 0x35, 0x99, 0x4f, 0xad, 0xe5, 0x5b,
0x5d, 0xb2, 0x7a, 0xf9, 0x1c, 0x5f, 0x29, 0x63, 0xa4, 0xe6, 0x16, 0x7b, 0x72, 0x0f, 0xe0, 0x24,
0x64, 0x54, 0xcc, 0x8f, 0x45, 0x24, 0xed, 0x8a, 0x79, 0x7f, 0x6f, 0x5f, 0xf8, 0x11, 0x9c, 0x7e,
0xc1, 0xa7, 0xff, 0x68, 0x49, 0xa1, 0x71, 0x0f, 0xea, 0x4b, 0xf4, 0x9a, 0x6c, 0x5f, 0x2f, 0x67,
0xbb, 0x56, 0xce, 0xce, 0x2d, 0xd8, 0x4c, 0x5f, 0x21, 0x21, 0xb0, 0xc1, 0xe8, 0x14, 0x33, 0x35,
0xb3, 0x6e, 0x7f, 0x0a, 0xb5, 0xa2, 0xe9, 0x90, 0x1e, 0x80, 0xc7, 0x19, 0x43, 0x4f, 0x71, 0x21,
0x6d, 0xcb, 0x04, 0x7a, 0xde, 0x9c, 0x06, 0x39, 0xe5, 0x96, 0xa4, 0xda, 0x77, 0xa1, 0x56, 0x10,
0xeb, 0x3c, 0x68, 0x4c, 0xcd, 0xe3, 0x3c, 0x30, 0xb3, 0x6e, 0xff, 0x50, 0x81, 0x52, 0xa3, 0x5a,
0xab, 0xb6, 0x0f, 0x9b, 0xa1, 0x94, 0x09, 0x8a, 0x4c, 0x31, 0xdb, 0x91, 0x0e, 0x54, 0xbd, 0x28,
0x44, 0xa6, 0x46, 0x43, 0xd3, 0x0b, 0x6b, 0xfd, 0x2b, 0x8b, 0xb3, 0x83, 0xea, 0x20, 0xc3, 0xdc,
0x82, 0x25, 0x87, 0xb0, 0xed, 0x45, 0x61, 0x4e, 0xa4, 0x2d, 0xaf, 0x5f, 0x5f, 0x9c, 0x1d, 0x6c,
0x0f, 0xc6, 0xa3, 0x42, 0xbe, 0x2c, 0xa3, 0x9d, 0x4a, 0x8f, 0xc7, 0x59, 0xe3, 0xab, 0xb9, 0xd9,
0x8e, 0xbc, 0x84, 0x9d, 0xd0, 0x7f, 0xce, 0x27, 0xc8, 0x06, 0x66, 0x08, 0xb0, 0x37, 0x4d, 0x6e,
0x6e, 0xaf, 0xe9, 0xc2, 0xce, 0xa8, 0x2c, 0x68, 0xae, 0xab, 0xbf, 0xb7, 0x38, 0x3b, 0xd8, 0x19,
0x0d, 0x4b, 0xb8, 0x7b, 0xd1, 0x5e, 0x63, 0x0e, 0x64, 0x55, 0x6f, 0xcd, 0x35, 0x3f, 0xbe, 0xf8,
0xa8, 0x3e, 0xfa, 0xd7, 0x47, 0x95, 0x4e, 0x31, 0x4e, 0x31, 0x86, 0xe9, 0x71, 0xc0, 0x31, 0xf6,
0x4b, 0xf5, 0xd1, 0xfb, 0x1a, 0xea, 0x79, 0x57, 0x7f, 0x86, 0x62, 0x16, 0x7a, 0x48, 0xbe, 0x80,
0xca, 0x03, 0x54, 0x64, 0x7f, 0xa5, 0xed, 0x9b, 0x51, 0xa7, 0xb1, 0xb7, 0x82, 0xb7, 0xed, 0xef,
0x7f, 0xff, 0xf3, 0xa7, 0x4b, 0x84, 0xec, 0x9a, 0xf1, 0x6d, 0x76, 0x58, 0x8c, 0x4e, 0xfd, 0xc1,
0xaf, 0x8b, 0xa6, 0xf5, 0xdb, 0xa2, 0x69, 0xfd, 0xb1, 0x68, 0x5a, 0x5f, 0x7d, 0xf8, 0xff, 0xc6,
0xb8, 0xf4, 0x0e, 0x0b, 0x23, 0x27, 0x9b, 0x66, 0xe8, 0xba, 0xfb, 0x77, 0x00, 0x00, 0x00, 0xff,
0xff, 0x2a, 0xfc, 0x97, 0xee, 0x63, 0x0a, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -873,6 +882,15 @@ func (m *Settings) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if len(m.ControllerNamespace) > 0 {
i -= len(m.ControllerNamespace)
copy(dAtA[i:], m.ControllerNamespace)
i = encodeVarintSettings(dAtA, i, uint64(len(m.ControllerNamespace)))
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0xba
}
if m.ExecEnabled {
i--
if m.ExecEnabled {
@ -1554,6 +1572,10 @@ func (m *Settings) Size() (n int) {
if m.ExecEnabled {
n += 3
}
l = len(m.ControllerNamespace)
if l > 0 {
n += 2 + l + sovSettings(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@ -2571,6 +2593,38 @@ func (m *Settings) Unmarshal(dAtA []byte) error {
}
}
m.ExecEnabled = bool(v != 0)
case 23:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ControllerNamespace", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSettings
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthSettings
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthSettings
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.ControllerNamespace = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipSettings(dAtA[iNdEx:])

View file

@ -5,6 +5,7 @@ API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/ap
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,AppProjectSpec,NamespaceResourceWhitelist
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,AppProjectSpec,Roles
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,AppProjectSpec,SignatureKeys
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,AppProjectSpec,SourceNamespaces
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,AppProjectSpec,SourceRepos
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSourceHelm,FileParameters
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSourceHelm,Parameters

View file

@ -172,6 +172,15 @@ func (p *AppProject) ValidateProject() error {
}
destKeys[key] = true
}
srcNamespaces := make(map[string]bool)
for _, ns := range p.Spec.SourceNamespaces {
if _, ok := srcNamespaces[ns]; ok {
return status.Errorf(codes.InvalidArgument, "source namespaces '%s' already added", ns)
}
destKeys[ns] = true
}
srcRepos := make(map[string]bool)
for _, src := range p.Spec.SourceRepos {
if _, ok := srcRepos[src]; ok {
@ -482,3 +491,18 @@ func jwtTokensCombine(tokens1 []JWTToken, tokens2 []JWTToken) []JWTToken {
})
return tokens
}
// IsAppNamespacePermitted checks whether an application that associates with
// this AppProject is allowed by comparing the Application's namespace with
// the list of allowed namespaces in the AppProject.
//
// Applications in the installation namespace are always permitted. Also, at
// application creation time, its namespace may yet be empty to indicate that
// the application will be created in the controller's namespace.
func (p AppProject) IsAppNamespacePermitted(app *Application, controllerNs string) bool {
if app.Namespace == "" || app.Namespace == controllerNs {
return true
}
return glob.MatchStringInList(p.Spec.SourceNamespaces, app.Namespace, false)
}

View file

@ -2604,433 +2604,434 @@ func init() {
}
var fileDescriptor_030104ce3b95bcac = []byte{
// 6809 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x5d, 0x8c, 0x24, 0xc9,
0x51, 0xf0, 0x55, 0xf7, 0xfc, 0x74, 0xc7, 0xfc, 0xec, 0x4e, 0xee, 0xcf, 0x8d, 0xe7, 0x3b, 0xef,
0xac, 0xea, 0x64, 0xfb, 0xbe, 0xcf, 0xf6, 0xcc, 0x77, 0xcb, 0xd9, 0x1c, 0x3e, 0x73, 0x66, 0x7a,
0x66, 0x7f, 0x66, 0x77, 0xfe, 0x36, 0x66, 0x76, 0x17, 0x9f, 0x8d, 0xb9, 0x9a, 0xea, 0xec, 0xee,
0xda, 0xe9, 0xae, 0xea, 0xab, 0xaa, 0x9e, 0x9d, 0xb6, 0xf1, 0x9f, 0x64, 0xf0, 0x49, 0xfe, 0x95,
0xcd, 0x83, 0x2d, 0x21, 0x30, 0x3f, 0x42, 0xe2, 0xc1, 0x02, 0x9e, 0x00, 0x21, 0x1e, 0x30, 0x2f,
0x46, 0x3c, 0x60, 0x09, 0x84, 0x0d, 0x16, 0x83, 0xbd, 0x80, 0x0c, 0x48, 0x80, 0x00, 0xbf, 0xb0,
0xe2, 0x01, 0xe5, 0x4f, 0x65, 0x66, 0x55, 0x77, 0xef, 0xcc, 0x6c, 0xd7, 0x2e, 0x96, 0xc5, 0xdb,
0x74, 0x44, 0x64, 0x44, 0x64, 0x56, 0x66, 0x64, 0x44, 0x64, 0x64, 0x0e, 0xac, 0xd5, 0xbd, 0xb8,
0xd1, 0xd9, 0x5d, 0x70, 0x83, 0xd6, 0xa2, 0x13, 0xd6, 0x83, 0x76, 0x18, 0xdc, 0xe5, 0x7f, 0xbc,
0xdd, 0xad, 0x2e, 0xee, 0x5f, 0x5a, 0x6c, 0xef, 0xd5, 0x17, 0x9d, 0xb6, 0x17, 0x2d, 0x3a, 0xed,
0x76, 0xd3, 0x73, 0x9d, 0xd8, 0x0b, 0xfc, 0xc5, 0xfd, 0xe7, 0x9d, 0x66, 0xbb, 0xe1, 0x3c, 0xbf,
0x58, 0xa7, 0x3e, 0x0d, 0x9d, 0x98, 0x56, 0x17, 0xda, 0x61, 0x10, 0x07, 0xe4, 0xdd, 0x9a, 0xdb,
0x42, 0xc2, 0x8d, 0xff, 0xf1, 0xd3, 0x6e, 0x75, 0x61, 0xff, 0xd2, 0x42, 0x7b, 0xaf, 0xbe, 0xc0,
0xb8, 0x2d, 0x18, 0xdc, 0x16, 0x12, 0x6e, 0x73, 0x6f, 0x37, 0x74, 0xa9, 0x07, 0xf5, 0x60, 0x91,
0x33, 0xdd, 0xed, 0xd4, 0xf8, 0x2f, 0xfe, 0x83, 0xff, 0x25, 0x84, 0xcd, 0xd9, 0x7b, 0x2f, 0x46,
0x0b, 0x5e, 0xc0, 0xd4, 0x5b, 0x74, 0x83, 0x90, 0x2e, 0xee, 0xf7, 0x28, 0x34, 0xf7, 0x82, 0xa6,
0x69, 0x39, 0x6e, 0xc3, 0xf3, 0x69, 0xd8, 0xd5, 0x7d, 0x6a, 0xd1, 0xd8, 0xe9, 0xd7, 0x6a, 0x71,
0x50, 0xab, 0xb0, 0xe3, 0xc7, 0x5e, 0x8b, 0xf6, 0x34, 0x78, 0xe7, 0x51, 0x0d, 0x22, 0xb7, 0x41,
0x5b, 0x4e, 0xb6, 0x9d, 0xfd, 0x1a, 0x4c, 0x2d, 0xdd, 0xd9, 0x5e, 0xea, 0xc4, 0x8d, 0xe5, 0xc0,
0xaf, 0x79, 0x75, 0xf2, 0x0e, 0x98, 0x70, 0x9b, 0x9d, 0x28, 0xa6, 0xe1, 0x86, 0xd3, 0xa2, 0xb3,
0xd6, 0x45, 0xeb, 0xb9, 0x72, 0xe5, 0xcc, 0xd7, 0x0f, 0xe7, 0x9f, 0xba, 0x7f, 0x38, 0x3f, 0xb1,
0xac, 0x51, 0x68, 0xd2, 0x91, 0xff, 0x0b, 0xe3, 0x61, 0xd0, 0xa4, 0x4b, 0xb8, 0x31, 0x5b, 0xe0,
0x4d, 0x4e, 0xc9, 0x26, 0xe3, 0x28, 0xc0, 0x98, 0xe0, 0xed, 0xbf, 0x28, 0x00, 0x2c, 0xb5, 0xdb,
0x5b, 0x61, 0x70, 0x97, 0xba, 0x31, 0x79, 0x15, 0x4a, 0x6c, 0x14, 0xaa, 0x4e, 0xec, 0x70, 0x69,
0x13, 0x97, 0xfe, 0xff, 0x82, 0xe8, 0xcc, 0x82, 0xd9, 0x19, 0xfd, 0xe5, 0x18, 0xf5, 0xc2, 0xfe,
0xf3, 0x0b, 0x9b, 0xbb, 0xac, 0xfd, 0x3a, 0x8d, 0x9d, 0x0a, 0x91, 0xc2, 0x40, 0xc3, 0x50, 0x71,
0x25, 0x3e, 0x8c, 0x44, 0x6d, 0xea, 0x72, 0xc5, 0x26, 0x2e, 0xad, 0x2d, 0x0c, 0x33, 0x45, 0x16,
0xb4, 0xe6, 0xdb, 0x6d, 0xea, 0x56, 0x26, 0xa5, 0xe4, 0x11, 0xf6, 0x0b, 0xb9, 0x1c, 0xb2, 0x0f,
0x63, 0x51, 0xec, 0xc4, 0x9d, 0x68, 0xb6, 0xc8, 0x25, 0x6e, 0xe4, 0x26, 0x91, 0x73, 0xad, 0x4c,
0x4b, 0x99, 0x63, 0xe2, 0x37, 0x4a, 0x69, 0xf6, 0x5f, 0x5b, 0x30, 0xad, 0x89, 0xd7, 0xbc, 0x28,
0x26, 0xef, 0xef, 0x19, 0xdc, 0x85, 0xe3, 0x0d, 0x2e, 0x6b, 0xcd, 0x87, 0xf6, 0xb4, 0x14, 0x56,
0x4a, 0x20, 0xc6, 0xc0, 0xb6, 0x60, 0xd4, 0x8b, 0x69, 0x2b, 0x9a, 0x2d, 0x5c, 0x2c, 0x3e, 0x37,
0x71, 0xe9, 0x5a, 0x5e, 0xfd, 0xac, 0x4c, 0x49, 0xa1, 0xa3, 0xab, 0x8c, 0x3d, 0x0a, 0x29, 0xf6,
0xf7, 0xc1, 0xec, 0x1f, 0x1b, 0x70, 0xf2, 0x3c, 0x4c, 0x44, 0x41, 0x27, 0x74, 0x29, 0xd2, 0x76,
0x10, 0xcd, 0x5a, 0x17, 0x8b, 0x6c, 0xea, 0xb1, 0x99, 0xba, 0xad, 0xc1, 0x68, 0xd2, 0x90, 0xcf,
0x5a, 0x30, 0x59, 0xa5, 0x51, 0xec, 0xf9, 0x5c, 0x7e, 0xa2, 0xfc, 0xce, 0xd0, 0xca, 0x27, 0xc0,
0x15, 0xcd, 0xbc, 0x72, 0x56, 0x76, 0x64, 0xd2, 0x00, 0x46, 0x98, 0x92, 0xcf, 0x56, 0x5c, 0x95,
0x46, 0x6e, 0xe8, 0xb5, 0xd9, 0x6f, 0x3e, 0x67, 0x8c, 0x15, 0xb7, 0xa2, 0x51, 0x68, 0xd2, 0x11,
0x1f, 0x46, 0xd9, 0x8a, 0x8a, 0x66, 0x47, 0xb8, 0xfe, 0xab, 0xc3, 0xe9, 0x2f, 0x07, 0x95, 0x2d,
0x56, 0x3d, 0xfa, 0xec, 0x57, 0x84, 0x42, 0x0c, 0xf9, 0x8c, 0x05, 0xb3, 0x72, 0xc5, 0x23, 0x15,
0x03, 0x7a, 0xa7, 0xe1, 0xc5, 0xb4, 0xe9, 0x45, 0xf1, 0xec, 0x28, 0xd7, 0x61, 0xf1, 0x78, 0x73,
0xeb, 0x6a, 0x18, 0x74, 0xda, 0x37, 0x3c, 0xbf, 0x5a, 0xb9, 0x28, 0x25, 0xcd, 0x2e, 0x0f, 0x60,
0x8c, 0x03, 0x45, 0x92, 0x2f, 0x5a, 0x30, 0xe7, 0x3b, 0x2d, 0x1a, 0xb5, 0x1d, 0xf6, 0x69, 0x05,
0xba, 0xd2, 0x74, 0xdc, 0x3d, 0xae, 0xd1, 0xd8, 0xa3, 0x69, 0x64, 0x4b, 0x8d, 0xe6, 0x36, 0x06,
0xb2, 0xc6, 0x87, 0x88, 0x25, 0xbf, 0x6a, 0xc1, 0x4c, 0x10, 0xb6, 0x1b, 0x8e, 0x4f, 0xab, 0x09,
0x36, 0x9a, 0x1d, 0xe7, 0x4b, 0xef, 0x03, 0xc3, 0x7d, 0xa2, 0xcd, 0x2c, 0xdb, 0xf5, 0xc0, 0xf7,
0xe2, 0x20, 0xdc, 0xa6, 0x71, 0xec, 0xf9, 0xf5, 0xa8, 0x72, 0xee, 0xfe, 0xe1, 0xfc, 0x4c, 0x0f,
0x15, 0xf6, 0xea, 0x43, 0x3e, 0x04, 0x13, 0x51, 0xd7, 0x77, 0xef, 0x78, 0x7e, 0x35, 0xb8, 0x17,
0xcd, 0x96, 0xf2, 0x58, 0xbe, 0xdb, 0x8a, 0xa1, 0x5c, 0x80, 0x5a, 0x00, 0x9a, 0xd2, 0xfa, 0x7f,
0x38, 0x3d, 0x95, 0xca, 0x79, 0x7f, 0x38, 0x3d, 0x99, 0x1e, 0x22, 0x96, 0x7c, 0xd2, 0x82, 0xa9,
0xc8, 0xab, 0xfb, 0x4e, 0xdc, 0x09, 0xe9, 0x0d, 0xda, 0x8d, 0x66, 0x81, 0x2b, 0x72, 0x7d, 0xc8,
0x51, 0x31, 0x58, 0x56, 0xce, 0x49, 0x1d, 0xa7, 0x4c, 0x68, 0x84, 0x69, 0xb9, 0xfd, 0x16, 0x9a,
0x9e, 0xd6, 0x13, 0xf9, 0x2e, 0x34, 0x3d, 0xa9, 0x07, 0x8a, 0xb4, 0xff, 0xb8, 0x00, 0xa7, 0xb3,
0x7b, 0x10, 0xf9, 0x75, 0x0b, 0x4e, 0xdd, 0xbd, 0x17, 0xef, 0x04, 0x7b, 0xd4, 0x8f, 0x2a, 0x5d,
0x66, 0x29, 0xb8, 0xf5, 0x9d, 0xb8, 0xe4, 0xe6, 0xbb, 0xdb, 0x2d, 0x5c, 0x4f, 0x4b, 0xb9, 0xec,
0xc7, 0x61, 0xb7, 0xf2, 0xb4, 0xec, 0xcf, 0xa9, 0xeb, 0x77, 0x76, 0x4c, 0x2c, 0x66, 0x95, 0x9a,
0xfb, 0x94, 0x05, 0x67, 0xfb, 0xb1, 0x20, 0xa7, 0xa1, 0xb8, 0x47, 0xbb, 0xc2, 0xc1, 0x41, 0xf6,
0x27, 0xf9, 0x29, 0x18, 0xdd, 0x77, 0x9a, 0x1d, 0x2a, 0x1d, 0x85, 0xab, 0xc3, 0x75, 0x44, 0x69,
0x86, 0x82, 0xeb, 0xbb, 0x0a, 0x2f, 0x5a, 0xf6, 0x9f, 0x16, 0x61, 0xc2, 0xd8, 0x2a, 0x9e, 0x80,
0xf3, 0x13, 0xa4, 0x9c, 0x9f, 0xf5, 0xdc, 0x76, 0xb9, 0x81, 0xde, 0xcf, 0xbd, 0x8c, 0xf7, 0xb3,
0x99, 0x9f, 0xc8, 0x87, 0xba, 0x3f, 0x24, 0x86, 0x72, 0xd0, 0x66, 0xce, 0x2d, 0xdb, 0x45, 0x47,
0xf2, 0xf8, 0x84, 0x9b, 0x09, 0xbb, 0xca, 0xd4, 0xfd, 0xc3, 0xf9, 0xb2, 0xfa, 0x89, 0x5a, 0x90,
0xfd, 0x4d, 0x0b, 0xce, 0x1a, 0x3a, 0x2e, 0x07, 0x7e, 0xd5, 0xe3, 0x9f, 0xf6, 0x22, 0x8c, 0xc4,
0xdd, 0x76, 0xe2, 0x41, 0xab, 0x91, 0xda, 0xe9, 0xb6, 0x29, 0x72, 0x0c, 0xf3, 0x99, 0x5b, 0x34,
0x8a, 0x9c, 0x3a, 0xcd, 0xfa, 0xcc, 0xeb, 0x02, 0x8c, 0x09, 0x9e, 0x84, 0x40, 0x9a, 0x4e, 0x14,
0xef, 0x84, 0x8e, 0x1f, 0x71, 0xf6, 0x3b, 0x5e, 0x8b, 0xca, 0x01, 0xfe, 0x7f, 0xc7, 0x9b, 0x31,
0xac, 0x45, 0xe5, 0xfc, 0xfd, 0xc3, 0x79, 0xb2, 0xd6, 0xc3, 0x09, 0xfb, 0x70, 0xb7, 0xbf, 0x68,
0xc1, 0xf9, 0xfe, 0x6e, 0x0d, 0x79, 0x33, 0x8c, 0x45, 0x34, 0xdc, 0xa7, 0xa1, 0xec, 0x9d, 0xfe,
0x24, 0x1c, 0x8a, 0x12, 0x4b, 0x16, 0xa1, 0xac, 0x4c, 0xae, 0xec, 0xe3, 0x8c, 0x24, 0x2d, 0x6b,
0x3b, 0xad, 0x69, 0xd8, 0xa0, 0xb1, 0x1f, 0xd2, 0x09, 0x52, 0x83, 0xc6, 0xe3, 0x0d, 0x8e, 0xb1,
0xff, 0xc6, 0x82, 0x53, 0x86, 0x56, 0x4f, 0xc0, 0xcb, 0xf5, 0xd3, 0x5e, 0xee, 0x6a, 0x6e, 0xf3,
0x79, 0x80, 0x9b, 0xfb, 0x47, 0xa3, 0x30, 0x63, 0xce, 0x7a, 0x6e, 0x8e, 0x79, 0x80, 0x45, 0xdb,
0xc1, 0x2d, 0x5c, 0x93, 0x63, 0xae, 0x03, 0x2c, 0x01, 0xc6, 0x04, 0xcf, 0x06, 0xb1, 0xed, 0xc4,
0x0d, 0x39, 0xe0, 0x6a, 0x10, 0xb7, 0x9c, 0xb8, 0x81, 0x1c, 0x43, 0x5e, 0x86, 0xe9, 0xd8, 0x09,
0xeb, 0x34, 0x46, 0xba, 0xef, 0x45, 0xc9, 0x7a, 0x29, 0x57, 0xce, 0x4b, 0xda, 0xe9, 0x9d, 0x14,
0x16, 0x33, 0xd4, 0xe4, 0x35, 0x18, 0x69, 0xd0, 0x66, 0x4b, 0xfa, 0x35, 0xdb, 0xf9, 0xad, 0x70,
0xde, 0xd7, 0x6b, 0xb4, 0xd9, 0xaa, 0x94, 0x98, 0xca, 0xec, 0x2f, 0xe4, 0xa2, 0xc8, 0xcf, 0x5a,
0x50, 0xde, 0xeb, 0x44, 0x71, 0xd0, 0xf2, 0x3e, 0x48, 0x67, 0x4b, 0x5c, 0xf0, 0x4f, 0xe6, 0x2c,
0xf8, 0x46, 0xc2, 0x5f, 0xac, 0x77, 0xf5, 0x13, 0xb5, 0x64, 0xae, 0x47, 0xd5, 0x0b, 0xa9, 0x1b,
0x07, 0x61, 0x77, 0x16, 0x1e, 0x8b, 0x1e, 0x2b, 0x09, 0x7f, 0xa1, 0x87, 0xfa, 0x89, 0x5a, 0x32,
0xe9, 0xc2, 0x58, 0xbb, 0xd9, 0xa9, 0x7b, 0xfe, 0xec, 0x04, 0xd7, 0xe1, 0x56, 0xce, 0x3a, 0x6c,
0x71, 0xe6, 0x15, 0x60, 0xab, 0x5a, 0xfc, 0x8d, 0x52, 0x20, 0x79, 0x16, 0x46, 0xdd, 0x86, 0x13,
0xc6, 0xb3, 0x93, 0x7c, 0xd2, 0xa8, 0x59, 0xbc, 0xcc, 0x80, 0x28, 0x70, 0xf6, 0x2f, 0x17, 0x60,
0x6e, 0x70, 0xc7, 0xc4, 0x74, 0x76, 0x3b, 0x61, 0x24, 0x0c, 0x64, 0xc9, 0x9c, 0xce, 0x1c, 0x8c,
0x09, 0x9e, 0x7c, 0xdc, 0x82, 0xf1, 0xbb, 0x51, 0xe0, 0xfb, 0x34, 0x96, 0xbb, 0xd8, 0xed, 0x9c,
0xfb, 0x7a, 0x5d, 0x70, 0xd7, 0x3a, 0x48, 0x00, 0x26, 0x72, 0x99, 0xba, 0xf4, 0xc0, 0x6d, 0x76,
0xaa, 0x89, 0x69, 0x52, 0xa4, 0x97, 0x05, 0x18, 0x13, 0x3c, 0x23, 0xf5, 0x7c, 0x41, 0x3a, 0x92,
0x26, 0x5d, 0xf5, 0x25, 0xa9, 0xc4, 0xdb, 0xbf, 0x35, 0x0a, 0xe7, 0xfa, 0xce, 0x7e, 0xb2, 0x00,
0xc0, 0x9d, 0x86, 0x2b, 0x1e, 0x8b, 0xf0, 0x44, 0x58, 0x3b, 0xcd, 0xf6, 0xf8, 0xdb, 0x0a, 0x8a,
0x06, 0x05, 0xf9, 0x28, 0x40, 0xdb, 0x09, 0x9d, 0x16, 0x8d, 0x69, 0x98, 0x18, 0xaa, 0x1b, 0xc3,
0x8d, 0x12, 0xd3, 0x63, 0x2b, 0xe1, 0xa9, 0x9d, 0x0c, 0x05, 0x8a, 0xd0, 0x10, 0xc9, 0x82, 0xd8,
0x90, 0x36, 0xa9, 0x13, 0xd1, 0x0d, 0x6d, 0xbf, 0x55, 0x10, 0x8b, 0x1a, 0x85, 0x26, 0x1d, 0xdb,
0x48, 0x78, 0x2f, 0x22, 0x39, 0x56, 0x6a, 0x23, 0xe1, 0xfd, 0x8c, 0x50, 0x62, 0xc9, 0xe7, 0x2c,
0x98, 0xae, 0x79, 0x4d, 0xaa, 0xa5, 0xcb, 0x90, 0x73, 0x73, 0xf8, 0x4e, 0x5e, 0x31, 0xf9, 0x6a,
0x13, 0x98, 0x02, 0x47, 0x98, 0x11, 0xcf, 0x3e, 0xf3, 0x3e, 0x0d, 0xb9, 0xed, 0x1c, 0x4b, 0x7f,
0xe6, 0xdb, 0x02, 0x8c, 0x09, 0x9e, 0x2c, 0xc1, 0xa9, 0xb6, 0x13, 0x45, 0xcb, 0x21, 0xad, 0x52,
0x3f, 0xf6, 0x9c, 0xa6, 0x08, 0x08, 0x4b, 0xda, 0x8b, 0xdd, 0x4a, 0xa3, 0x31, 0x4b, 0x4f, 0xde,
0x0b, 0x4f, 0x7b, 0x75, 0x3f, 0x08, 0xe9, 0xba, 0x17, 0x45, 0x9e, 0x5f, 0xd7, 0xd3, 0x80, 0x9b,
0xc2, 0x52, 0x65, 0x5e, 0xb2, 0x7a, 0x7a, 0xb5, 0x3f, 0x19, 0x0e, 0x6a, 0x4f, 0xde, 0x06, 0xa5,
0x68, 0xcf, 0x6b, 0x2f, 0x87, 0xd5, 0x68, 0xb6, 0xcc, 0x79, 0xa9, 0xcd, 0x70, 0x5b, 0xc2, 0x51,
0x51, 0xd8, 0x5f, 0x2e, 0xc0, 0xec, 0xa0, 0xf5, 0x43, 0x22, 0xb6, 0x4a, 0xe2, 0xdb, 0x4e, 0x18,
0xc9, 0x58, 0x60, 0xc8, 0x90, 0x52, 0xf2, 0xbd, 0xed, 0x84, 0xe6, 0x7a, 0xe3, 0x02, 0x30, 0x91,
0x44, 0xee, 0xc2, 0x48, 0xdc, 0x74, 0x72, 0xca, 0x41, 0x19, 0x12, 0xb5, 0xc7, 0xb6, 0xb6, 0x14,
0x21, 0x97, 0x41, 0x9e, 0x81, 0x91, 0xa6, 0xb7, 0xcb, 0x3c, 0x5b, 0xb6, 0x20, 0xf9, 0x16, 0xb5,
0xe6, 0xed, 0x46, 0xc8, 0xa1, 0xf6, 0xbf, 0x8e, 0xf5, 0x31, 0x79, 0x6a, 0x13, 0x21, 0x97, 0x00,
0x98, 0x07, 0xb3, 0x15, 0xd2, 0x9a, 0x77, 0x20, 0x37, 0x71, 0xb5, 0xac, 0x36, 0x14, 0x06, 0x0d,
0xaa, 0xa4, 0xcd, 0x76, 0xa7, 0xc6, 0xda, 0x14, 0x7a, 0xdb, 0x08, 0x0c, 0x1a, 0x54, 0xe4, 0x05,
0x18, 0xf3, 0x5a, 0x4e, 0x9d, 0x26, 0x6a, 0x3e, 0xc3, 0xd6, 0xd3, 0x2a, 0x87, 0x3c, 0x38, 0x9c,
0x9f, 0x56, 0x0a, 0x71, 0x10, 0x4a, 0x5a, 0xf2, 0x6b, 0x16, 0x4c, 0xba, 0x41, 0xab, 0x15, 0xf8,
0x6b, 0xce, 0x2e, 0x6d, 0x26, 0x69, 0xa5, 0xbb, 0x8f, 0x6b, 0x8b, 0x5d, 0x58, 0x36, 0x84, 0x89,
0xa0, 0x4e, 0x25, 0xcb, 0x4c, 0x14, 0xa6, 0xb4, 0x32, 0x97, 0xdd, 0xe8, 0x11, 0xcb, 0xee, 0x77,
0x2d, 0x98, 0x11, 0x6d, 0x97, 0x7c, 0x3f, 0x88, 0x65, 0xb6, 0x4f, 0xe4, 0x85, 0x82, 0xc7, 0xdc,
0x2d, 0x43, 0xa2, 0xe8, 0xdb, 0x1b, 0xa4, 0x9a, 0x33, 0x3d, 0x78, 0xec, 0x55, 0x92, 0x5c, 0x85,
0x99, 0x5a, 0x10, 0xba, 0xd4, 0x1c, 0x08, 0x69, 0x33, 0x14, 0xa3, 0x2b, 0x59, 0x02, 0xec, 0x6d,
0x43, 0x6e, 0xc3, 0x79, 0x03, 0x68, 0x8e, 0x83, 0x30, 0x1b, 0x17, 0x24, 0xb7, 0xf3, 0x57, 0xfa,
0x52, 0xe1, 0x80, 0xd6, 0x73, 0xef, 0x81, 0x99, 0x9e, 0xef, 0xd7, 0x27, 0xa2, 0x3e, 0x6b, 0x46,
0xd4, 0x65, 0x23, 0x10, 0x9e, 0x5b, 0x81, 0xf3, 0xfd, 0x47, 0xea, 0x24, 0x5c, 0xec, 0x5f, 0xb4,
0xe0, 0xe9, 0x01, 0x9e, 0x8b, 0x0a, 0x25, 0xac, 0x41, 0xa1, 0x04, 0x71, 0xa0, 0x48, 0xfd, 0x7d,
0x69, 0x38, 0xae, 0x0c, 0x37, 0x23, 0x2e, 0xfb, 0xfb, 0xe2, 0x43, 0x8f, 0xdf, 0x3f, 0x9c, 0x2f,
0x5e, 0xf6, 0xf7, 0x91, 0xf1, 0xb6, 0x7f, 0x7e, 0x2c, 0x15, 0xad, 0x6c, 0x27, 0x01, 0x32, 0x57,
0x54, 0xc6, 0x2a, 0x9b, 0x39, 0xcf, 0x45, 0x23, 0x1a, 0x13, 0x69, 0x6f, 0x29, 0x8e, 0x7c, 0xca,
0xe2, 0x99, 0xe6, 0x24, 0x8a, 0x93, 0xce, 0xd4, 0xe3, 0x49, 0x7c, 0x9b, 0xf9, 0xeb, 0x04, 0x88,
0xa6, 0x74, 0xb6, 0x92, 0xdb, 0x22, 0xd1, 0x93, 0x75, 0xa9, 0x92, 0x5c, 0x74, 0x82, 0x27, 0x07,
0x00, 0x51, 0xd7, 0x77, 0xb7, 0x82, 0xa6, 0xe7, 0x76, 0x65, 0x68, 0x9f, 0x43, 0xb6, 0x52, 0xf0,
0x13, 0x7e, 0x95, 0xfe, 0x8d, 0x86, 0x2c, 0xf2, 0x15, 0x0b, 0x66, 0xc4, 0xc6, 0xb9, 0xe2, 0xd5,
0x6a, 0x34, 0xa4, 0xbe, 0x4b, 0x13, 0xd7, 0xe3, 0xce, 0x70, 0x1a, 0x24, 0x89, 0xb6, 0xd5, 0x2c,
0x7b, 0xbd, 0xc4, 0x7b, 0x50, 0xd8, 0xab, 0x0c, 0xa9, 0xc2, 0x88, 0xe7, 0xd7, 0x02, 0x69, 0xd8,
0x2a, 0xc3, 0x29, 0xb5, 0xea, 0xd7, 0x02, 0xbd, 0x56, 0xd8, 0x2f, 0xe4, 0xdc, 0xc9, 0x1a, 0x9c,
0x0d, 0x65, 0xf4, 0x77, 0xcd, 0x8b, 0x98, 0x0b, 0xbf, 0xe6, 0xb5, 0xbc, 0x98, 0x1b, 0xa5, 0x62,
0x65, 0xf6, 0xfe, 0xe1, 0xfc, 0x59, 0xec, 0x83, 0xc7, 0xbe, 0xad, 0xec, 0xd7, 0xcb, 0xe9, 0x10,
0x57, 0x24, 0x70, 0x3e, 0x0c, 0xe5, 0x50, 0xa5, 0xcc, 0x85, 0x03, 0xb1, 0x96, 0xcf, 0x18, 0xcb,
0xcc, 0x91, 0xca, 0x3d, 0xe8, 0xe4, 0xb8, 0x96, 0xc8, 0x1c, 0x09, 0xf6, 0xe5, 0xe5, 0xb2, 0xc8,
0x61, 0x7e, 0x49, 0xa9, 0x3a, 0x49, 0xd6, 0xf5, 0x5d, 0xe4, 0x32, 0x48, 0x08, 0x63, 0x0d, 0xea,
0x34, 0xe3, 0x86, 0xcc, 0xe1, 0x5c, 0x1f, 0xd6, 0x8d, 0x65, 0xbc, 0xb2, 0xf9, 0x31, 0x01, 0x45,
0x29, 0x89, 0x1c, 0xc0, 0x78, 0x43, 0x7c, 0x04, 0xb9, 0xb7, 0xaf, 0x0f, 0x3b, 0xb8, 0xa9, 0x2f,
0xab, 0xd7, 0xaf, 0x04, 0x60, 0x22, 0x8e, 0xfc, 0x9c, 0x05, 0xe0, 0x26, 0x89, 0xb1, 0x64, 0xf9,
0x60, 0x6e, 0x76, 0x47, 0xe5, 0xdc, 0xb4, 0x6b, 0xa4, 0x40, 0x11, 0x1a, 0x92, 0xc9, 0xab, 0x30,
0x19, 0x52, 0x37, 0xf0, 0x5d, 0xaf, 0x49, 0xab, 0x4b, 0x31, 0xf7, 0xdc, 0x4f, 0x96, 0x40, 0x3b,
0xcd, 0xfc, 0x13, 0x34, 0x78, 0x60, 0x8a, 0x23, 0x79, 0xdd, 0x82, 0x69, 0x95, 0x1c, 0x64, 0x1f,
0x84, 0xca, 0x24, 0xc9, 0x5a, 0x4e, 0xa9, 0x48, 0xce, 0xb3, 0x42, 0x58, 0x84, 0x92, 0x86, 0x61,
0x46, 0x2e, 0x79, 0x05, 0x20, 0xd8, 0xe5, 0x89, 0x38, 0xd6, 0xd5, 0xd2, 0x89, 0xbb, 0x3a, 0x2d,
0x72, 0xca, 0x09, 0x07, 0x34, 0xb8, 0x91, 0x1b, 0x00, 0x62, 0xd9, 0xec, 0x74, 0xdb, 0x94, 0x87,
0x0d, 0xe5, 0xca, 0x5b, 0x93, 0xc1, 0xdf, 0x56, 0x98, 0x07, 0x87, 0xf3, 0xbd, 0x01, 0x2e, 0xcf,
0x80, 0x1a, 0xcd, 0xc9, 0x87, 0x60, 0x3c, 0xea, 0xb4, 0x5a, 0x8e, 0xca, 0xa7, 0x6c, 0xe5, 0xb7,
0x23, 0x0a, 0xbe, 0x7a, 0x6e, 0x4a, 0x00, 0x26, 0x12, 0x6d, 0x1f, 0x48, 0x2f, 0x3d, 0x79, 0x01,
0x26, 0xe9, 0x41, 0x4c, 0x43, 0xdf, 0x69, 0xde, 0xc2, 0xb5, 0x24, 0x02, 0xe7, 0x1f, 0xff, 0xb2,
0x01, 0xc7, 0x14, 0x15, 0xb1, 0x95, 0xe7, 0x5d, 0xe0, 0xf4, 0xa0, 0x3d, 0xef, 0xc4, 0xcf, 0xb6,
0xff, 0xb3, 0x90, 0xf2, 0x08, 0x76, 0x42, 0x4a, 0x49, 0x00, 0xa3, 0x7e, 0x50, 0x55, 0x46, 0xef,
0x7a, 0x3e, 0x46, 0x6f, 0x23, 0xa8, 0x1a, 0x67, 0xb9, 0xec, 0x57, 0x84, 0x42, 0x0e, 0x3f, 0xec,
0x4a, 0x4e, 0x05, 0x39, 0x42, 0x3a, 0x41, 0x79, 0x4a, 0x56, 0x87, 0x5d, 0x9b, 0xa6, 0x20, 0x4c,
0xcb, 0x25, 0x7b, 0x30, 0xda, 0x08, 0xa2, 0x58, 0xc4, 0x2a, 0x43, 0x7b, 0x61, 0xd7, 0x82, 0x28,
0xe6, 0x5b, 0x98, 0xea, 0x36, 0x83, 0x44, 0x28, 0x64, 0xd8, 0xdf, 0xb3, 0x52, 0xf9, 0x96, 0x3b,
0x4e, 0xec, 0x36, 0x2e, 0xef, 0x53, 0x9f, 0xcd, 0x67, 0x33, 0x59, 0xff, 0xa3, 0x66, 0xb2, 0xfe,
0xc1, 0xe1, 0xfc, 0x5b, 0x06, 0x15, 0xd7, 0xdc, 0x63, 0x1c, 0x16, 0x38, 0x0b, 0x23, 0xaf, 0xff,
0x31, 0x0b, 0x26, 0x0c, 0xf5, 0xe4, 0x86, 0x92, 0x63, 0xde, 0x58, 0x39, 0x57, 0x06, 0x10, 0x4d,
0x91, 0xf6, 0x17, 0x2c, 0x18, 0xaf, 0x38, 0xee, 0x5e, 0x50, 0xab, 0xb1, 0x00, 0xbf, 0xda, 0x91,
0xc7, 0x22, 0xa2, 0x7f, 0x2a, 0xc0, 0x5f, 0x91, 0x70, 0x54, 0x14, 0x6c, 0x0e, 0xd7, 0x1c, 0x37,
0x0e, 0x42, 0xae, 0x76, 0x51, 0xcc, 0xe1, 0x2b, 0x1c, 0x82, 0x12, 0x43, 0xde, 0x01, 0x13, 0x2d,
0xe7, 0x20, 0x69, 0x9c, 0x4d, 0xf6, 0xac, 0x6b, 0x14, 0x9a, 0x74, 0xf6, 0x1f, 0x96, 0x61, 0x5c,
0x9e, 0x3f, 0x1e, 0xfb, 0x04, 0x21, 0xf1, 0xe2, 0x0b, 0x03, 0xbd, 0xf8, 0x08, 0xc6, 0x5c, 0x5e,
0xba, 0x24, 0xb7, 0xd2, 0x21, 0xd3, 0x5e, 0x52, 0x41, 0x51, 0x0d, 0xa5, 0xd5, 0x12, 0xbf, 0x51,
0x8a, 0x22, 0x9f, 0xb7, 0xe0, 0x94, 0x1b, 0xf8, 0x3e, 0x75, 0xb5, 0x9d, 0x1f, 0xc9, 0xe3, 0x84,
0x6d, 0x39, 0xcd, 0x54, 0xa7, 0x88, 0x32, 0x08, 0xcc, 0x8a, 0x27, 0x2f, 0xc1, 0x94, 0x18, 0xb3,
0xdb, 0xa9, 0xf8, 0x58, 0x9f, 0x39, 0x9b, 0x48, 0x4c, 0xd3, 0x92, 0x05, 0x91, 0x67, 0xe0, 0x87,
0x30, 0x22, 0x46, 0x96, 0xf9, 0x46, 0x75, 0x4a, 0x13, 0xa1, 0x41, 0x41, 0x42, 0x20, 0x21, 0xad,
0x85, 0x34, 0x6a, 0x20, 0x7d, 0xad, 0x43, 0xa3, 0x98, 0xef, 0x31, 0xe3, 0x8f, 0x76, 0x1e, 0x85,
0x3d, 0x9c, 0xb0, 0x0f, 0x77, 0xb2, 0x27, 0x1d, 0xdd, 0x52, 0x1e, 0xcb, 0x49, 0x7e, 0xe6, 0x81,
0xfe, 0xee, 0x3c, 0x8c, 0x46, 0x0d, 0x27, 0xac, 0xf2, 0xbd, 0xad, 0x58, 0x29, 0x33, 0x5b, 0xb2,
0xcd, 0x00, 0x28, 0xe0, 0x64, 0x05, 0x4e, 0x67, 0x4e, 0xcc, 0x23, 0xbe, 0x7b, 0x95, 0x2a, 0xb3,
0x92, 0xdd, 0xe9, 0xcc, 0x59, 0x7b, 0x84, 0x3d, 0x2d, 0xcc, 0x20, 0x68, 0xe2, 0x88, 0x20, 0xa8,
0x0b, 0x63, 0x4d, 0x91, 0x08, 0x98, 0xe4, 0xa6, 0xf2, 0x66, 0x2e, 0x03, 0xb0, 0x60, 0x26, 0x60,
0xd4, 0x6c, 0x97, 0x09, 0x05, 0x29, 0x90, 0x7c, 0x86, 0x19, 0x34, 0x23, 0x77, 0x30, 0xc5, 0x15,
0xb8, 0x9d, 0x8f, 0x02, 0x3d, 0xa9, 0x12, 0x6d, 0xdd, 0x8c, 0x44, 0x84, 0x29, 0x7f, 0xee, 0xc7,
0x60, 0xe2, 0x51, 0xf3, 0x0e, 0x2f, 0xc3, 0xe9, 0xa1, 0x32, 0x0e, 0xdf, 0xb7, 0x20, 0xf9, 0xae,
0xcb, 0x8e, 0xdb, 0xa0, 0x6c, 0xca, 0x90, 0x97, 0x61, 0x5a, 0x85, 0x11, 0xcb, 0x41, 0xc7, 0x8f,
0x39, 0xaf, 0xa2, 0xce, 0x25, 0x63, 0x0a, 0x8b, 0x19, 0x6a, 0xb2, 0x08, 0x65, 0x36, 0x4e, 0xa2,
0xa9, 0x30, 0xbb, 0x2a, 0x54, 0x59, 0xda, 0x5a, 0x95, 0xad, 0x34, 0x0d, 0x09, 0x60, 0xa6, 0xe9,
0x44, 0x31, 0xd7, 0x80, 0x45, 0x15, 0x8f, 0x78, 0x1a, 0xcc, 0x0b, 0x86, 0xd6, 0xb2, 0x8c, 0xb0,
0x97, 0xb7, 0xfd, 0xcd, 0x11, 0x98, 0x4a, 0x59, 0x46, 0xb6, 0xab, 0x74, 0x22, 0xe6, 0xfa, 0xa8,
0x14, 0x8b, 0xda, 0x55, 0x6e, 0x49, 0x38, 0x2a, 0x0a, 0x46, 0xdd, 0x76, 0xa2, 0xe8, 0x5e, 0x10,
0x56, 0xa5, 0x29, 0x57, 0xd4, 0x5b, 0x12, 0x8e, 0x8a, 0x82, 0xed, 0x2f, 0xbb, 0xd4, 0x09, 0x69,
0xc8, 0x0b, 0x28, 0xb2, 0xfb, 0x4b, 0x45, 0xa3, 0xd0, 0xa4, 0xe3, 0x46, 0x39, 0x6e, 0x46, 0xcb,
0x4d, 0x8f, 0xfa, 0xb1, 0x50, 0x33, 0x1f, 0xa3, 0xbc, 0xb3, 0xb6, 0x6d, 0x32, 0xd5, 0x46, 0x39,
0x83, 0xc0, 0xac, 0x78, 0xf2, 0x09, 0x0b, 0xa6, 0x9c, 0x7b, 0x91, 0xae, 0xaf, 0xe5, 0x56, 0x79,
0xe8, 0x4d, 0x2a, 0x55, 0xb2, 0x5b, 0x99, 0x61, 0xe6, 0x3d, 0x05, 0xc2, 0xb4, 0x50, 0xf2, 0x25,
0x0b, 0x08, 0x3d, 0xa0, 0xee, 0x56, 0x18, 0xec, 0x7b, 0xd5, 0xe4, 0x1b, 0xca, 0xf0, 0x67, 0x48,
0x6f, 0xfb, 0x72, 0x0f, 0x5f, 0x61, 0xd5, 0x7b, 0xe1, 0xd8, 0x47, 0x07, 0xfb, 0xaf, 0x8a, 0x30,
0x61, 0x18, 0xe3, 0xbe, 0x3b, 0xab, 0xf5, 0x03, 0xb6, 0xb3, 0x16, 0x4e, 0xb0, 0xb3, 0x7e, 0x14,
0xca, 0x6e, 0x62, 0x28, 0xf2, 0xa9, 0x07, 0xce, 0x9a, 0x1f, 0x6d, 0x2b, 0x14, 0x08, 0xb5, 0x4c,
0x72, 0x15, 0x66, 0x0c, 0x36, 0xd2, 0xc8, 0x8c, 0x70, 0x23, 0xa3, 0x12, 0x4d, 0x4b, 0x59, 0x02,
0xec, 0x6d, 0x43, 0x9e, 0x67, 0x5e, 0xad, 0x27, 0xfb, 0x25, 0xa2, 0x78, 0x59, 0x6b, 0xbb, 0xb4,
0xb5, 0x9a, 0x80, 0xd1, 0xa4, 0xb1, 0xbf, 0x69, 0xa9, 0x8f, 0xfb, 0x04, 0x0a, 0x35, 0xee, 0xa6,
0x0b, 0x35, 0x2e, 0xe7, 0x32, 0xcc, 0x03, 0x8a, 0x34, 0x36, 0x60, 0x7c, 0x39, 0x68, 0xb5, 0x1c,
0xbf, 0x4a, 0xde, 0x04, 0xe3, 0xae, 0xf8, 0x53, 0x86, 0x89, 0x13, 0x6c, 0xff, 0x96, 0x58, 0x4c,
0x70, 0xe4, 0x19, 0x18, 0x71, 0xc2, 0x7a, 0x12, 0x1a, 0xf2, 0xb3, 0xa3, 0xa5, 0xb0, 0x1e, 0x21,
0x87, 0xda, 0x5f, 0x2c, 0x00, 0x2c, 0x07, 0xad, 0xb6, 0x13, 0xd2, 0xea, 0x4e, 0xf0, 0xbf, 0x39,
0x62, 0x11, 0x31, 0x7c, 0xda, 0x02, 0xc2, 0x46, 0x25, 0xf0, 0xa9, 0x1f, 0xab, 0xc3, 0x57, 0xb6,
0x5f, 0xba, 0x09, 0x54, 0x6e, 0x3e, 0x7a, 0x0d, 0x24, 0x08, 0xd4, 0x34, 0xc7, 0x88, 0x22, 0x9e,
0x4d, 0x76, 0xfc, 0x62, 0xba, 0xa6, 0x81, 0x1f, 0x94, 0x4a, 0x07, 0xc0, 0xfe, 0x5a, 0x01, 0xce,
0x0b, 0xb3, 0xb5, 0xee, 0xf8, 0x4e, 0x9d, 0xb6, 0x98, 0x56, 0xc7, 0x3d, 0x6d, 0x70, 0x99, 0xfb,
0xea, 0x25, 0x25, 0x0c, 0xc3, 0x4e, 0x4e, 0x31, 0xa9, 0xc4, 0x34, 0x5a, 0xf5, 0xbd, 0x18, 0x39,
0x73, 0x12, 0x41, 0x29, 0xb9, 0xe1, 0x21, 0x8d, 0x4d, 0x4e, 0x82, 0xd4, 0xba, 0xbb, 0x2a, 0xd9,
0xa3, 0x12, 0xc4, 0x36, 0xf7, 0x66, 0xe0, 0xee, 0x21, 0x6d, 0x07, 0xdc, 0xb0, 0x18, 0x27, 0xc8,
0x6b, 0x12, 0x8e, 0x8a, 0xc2, 0xfe, 0x9a, 0x05, 0x59, 0x93, 0xcb, 0xa3, 0x41, 0x51, 0x33, 0x98,
0x8d, 0x06, 0xd3, 0x25, 0x7e, 0x27, 0xa8, 0x98, 0x7b, 0x3f, 0x4c, 0x38, 0x71, 0x4c, 0x5b, 0x6d,
0x11, 0x9a, 0x14, 0x1f, 0x2d, 0xfd, 0xb5, 0x1e, 0x54, 0xbd, 0x9a, 0xc7, 0x43, 0x12, 0x93, 0x9d,
0x7d, 0x13, 0x4a, 0xc9, 0x89, 0xcf, 0x31, 0x3e, 0xfd, 0xb3, 0x29, 0x77, 0x72, 0xc0, 0xe4, 0x7a,
0x50, 0x80, 0x3e, 0x7b, 0x26, 0xeb, 0xb2, 0xb6, 0x2e, 0xa9, 0x2e, 0x9f, 0xcc, 0xc2, 0x90, 0x03,
0x71, 0xda, 0x25, 0xf2, 0x2c, 0xef, 0xcd, 0x7b, 0xcf, 0xd7, 0x07, 0x60, 0x13, 0x52, 0x3f, 0x75,
0x08, 0x46, 0x2e, 0x01, 0xe8, 0x4d, 0x41, 0x16, 0x7a, 0xa8, 0x4c, 0xad, 0xde, 0x3b, 0xd0, 0xa0,
0x62, 0x2e, 0xa0, 0xe7, 0x47, 0xb1, 0xd3, 0x6c, 0x5e, 0xf3, 0xfc, 0x58, 0xc6, 0xb2, 0xca, 0x60,
0xac, 0x6a, 0x14, 0x9a, 0x74, 0x73, 0xef, 0x34, 0xbe, 0xcb, 0x49, 0xdc, 0xfa, 0x4f, 0x17, 0x60,
0xfa, 0xaa, 0xdf, 0xd9, 0xba, 0xba, 0xd5, 0xd9, 0x6d, 0x7a, 0xee, 0x0d, 0xda, 0x65, 0x1f, 0x6d,
0x8f, 0x76, 0x57, 0x57, 0xe4, 0xb0, 0xab, 0x8f, 0x76, 0x83, 0x01, 0x51, 0xe0, 0x98, 0x9a, 0x35,
0xcf, 0xaf, 0xd3, 0xb0, 0x1d, 0x7a, 0xd2, 0x77, 0x37, 0xd4, 0xbc, 0xa2, 0x51, 0x68, 0xd2, 0x31,
0xde, 0xc1, 0x3d, 0x9f, 0x86, 0x59, 0x6b, 0xb3, 0xc9, 0x80, 0x28, 0x70, 0x8c, 0x28, 0x0e, 0x3b,
0x51, 0x2c, 0x47, 0x4c, 0x11, 0xed, 0x30, 0x20, 0x0a, 0x1c, 0x9b, 0x1e, 0x51, 0x67, 0x97, 0x67,
0x61, 0x33, 0xe7, 0xe1, 0xdb, 0x02, 0x8c, 0x09, 0x9e, 0x91, 0xee, 0xd1, 0xee, 0x0a, 0xdb, 0x7b,
0x33, 0x15, 0x2b, 0x37, 0x04, 0x18, 0x13, 0xbc, 0xfd, 0xf7, 0x16, 0x90, 0xf4, 0x70, 0x3c, 0x81,
0xed, 0xfb, 0xb5, 0xf4, 0xf6, 0x3d, 0x64, 0xc2, 0x3c, 0xad, 0xfe, 0x80, 0x5d, 0xfc, 0x57, 0x2c,
0x98, 0x34, 0xcf, 0x4e, 0x48, 0x3d, 0x63, 0x88, 0x36, 0xd3, 0x86, 0xe8, 0xc1, 0xe1, 0xfc, 0x8f,
0xf7, 0xbb, 0xae, 0x58, 0xf7, 0xe2, 0xa0, 0x1d, 0xbd, 0x9d, 0xfa, 0x75, 0xcf, 0xa7, 0x3c, 0x33,
0x28, 0xce, 0x5c, 0x52, 0x07, 0x33, 0xcb, 0x41, 0x95, 0x3e, 0x82, 0x25, 0xb3, 0xef, 0xc0, 0x4c,
0x4f, 0x99, 0xd2, 0x31, 0x8c, 0xce, 0x91, 0x55, 0xa0, 0x36, 0xc2, 0x04, 0x63, 0xbc, 0xd9, 0x16,
0x87, 0x23, 0xcb, 0x30, 0x23, 0xaa, 0xad, 0x98, 0xa4, 0x6d, 0xb7, 0x41, 0x5b, 0xaa, 0xf4, 0x8c,
0x07, 0x8a, 0xb7, 0xb3, 0x48, 0xec, 0xa5, 0xb7, 0x3f, 0x63, 0xc1, 0x54, 0xaa, 0x72, 0x2c, 0x27,
0xf3, 0xc8, 0x57, 0x5a, 0xc0, 0x8f, 0xf2, 0x42, 0xcf, 0x17, 0xb9, 0xbe, 0x92, 0xb1, 0xd2, 0x34,
0x0a, 0x4d, 0x3a, 0xfb, 0x0b, 0x05, 0x28, 0x25, 0x59, 0xe1, 0x63, 0xa8, 0xf2, 0x29, 0x0b, 0xa6,
0x54, 0x70, 0xce, 0x5d, 0x76, 0x31, 0x19, 0x37, 0x86, 0xcf, 0x4b, 0xab, 0xf3, 0x5e, 0xe6, 0xb2,
0xab, 0xd8, 0x01, 0x4d, 0x61, 0x98, 0x96, 0x4d, 0x6e, 0x03, 0x44, 0xdd, 0x28, 0xa6, 0x2d, 0x23,
0x78, 0xb0, 0x8d, 0x15, 0xb7, 0xe0, 0x06, 0x21, 0x65, 0xeb, 0x6b, 0x23, 0xa8, 0xd2, 0x6d, 0x45,
0xa9, 0x8d, 0xab, 0x86, 0xa1, 0xc1, 0xc9, 0xfe, 0xcd, 0x02, 0x9c, 0xce, 0xaa, 0x44, 0xde, 0x07,
0x93, 0x89, 0x74, 0xe3, 0xe6, 0x67, 0x92, 0x0a, 0x9f, 0x44, 0x03, 0xf7, 0xe0, 0x70, 0x7e, 0xbe,
0xf7, 0xea, 0xeb, 0x82, 0x49, 0x82, 0x29, 0x66, 0x22, 0x43, 0x22, 0x53, 0x79, 0x95, 0xee, 0x52,
0xbb, 0x2d, 0xd3, 0x1c, 0x46, 0x86, 0xc4, 0xc4, 0x62, 0x86, 0x9a, 0x6c, 0xc1, 0x59, 0x03, 0xb2,
0x41, 0xbd, 0x7a, 0x63, 0x37, 0x08, 0xc5, 0x15, 0x83, 0x62, 0xe5, 0x19, 0xc9, 0xe5, 0x2c, 0xf6,
0xa1, 0xc1, 0xbe, 0x2d, 0x99, 0xd3, 0xe2, 0x3a, 0x6d, 0xc7, 0xf5, 0xe2, 0xae, 0x8c, 0x86, 0x94,
0x6d, 0x5a, 0x96, 0x70, 0x54, 0x14, 0xf6, 0x3a, 0x8c, 0x1c, 0x73, 0x06, 0x1d, 0x6b, 0xaf, 0xbf,
0x09, 0x25, 0xc6, 0x8e, 0xd9, 0xa2, 0xbc, 0x58, 0x06, 0x50, 0x4a, 0x6e, 0x9c, 0x10, 0x1b, 0x8a,
0x9e, 0x93, 0x24, 0xa1, 0x54, 0xb7, 0x56, 0xa3, 0xa8, 0xc3, 0x3d, 0x19, 0x86, 0x24, 0xcf, 0x42,
0x91, 0x1e, 0xb4, 0xb3, 0xd9, 0xa6, 0xcb, 0x07, 0x6d, 0x2f, 0xa4, 0x11, 0x23, 0xa2, 0x07, 0x6d,
0x32, 0x07, 0x05, 0xaf, 0x2a, 0x37, 0x29, 0x90, 0x34, 0x85, 0xd5, 0x15, 0x2c, 0x78, 0x55, 0xfb,
0x00, 0xca, 0xea, 0x8a, 0x0b, 0xd9, 0x4b, 0x6c, 0xb7, 0x95, 0xc7, 0x31, 0x4e, 0xc2, 0x77, 0x80,
0xd5, 0xee, 0x00, 0xe8, 0x3a, 0xbd, 0xbc, 0xec, 0xcb, 0x45, 0x18, 0x71, 0x03, 0x59, 0xde, 0x5b,
0xd2, 0x6c, 0xb8, 0xd1, 0xe6, 0x18, 0xfb, 0x0e, 0x4c, 0xdf, 0xf0, 0x83, 0x7b, 0x3e, 0xdb, 0x4c,
0xaf, 0x78, 0xb4, 0x59, 0x65, 0x8c, 0x6b, 0xec, 0x8f, 0xac, 0x8b, 0xc0, 0xb1, 0x28, 0x70, 0xea,
0x1e, 0x48, 0x61, 0xd0, 0x3d, 0x10, 0xfb, 0x63, 0x16, 0x9c, 0x56, 0x05, 0x64, 0x89, 0x35, 0x7e,
0x11, 0x26, 0x77, 0x3b, 0x5e, 0xb3, 0x2a, 0x7f, 0x4b, 0x11, 0xaa, 0x44, 0xae, 0x62, 0xe0, 0x30,
0x45, 0xc9, 0xdc, 0xad, 0x5d, 0xcf, 0x77, 0xc2, 0xee, 0x96, 0x36, 0xff, 0xca, 0x22, 0x54, 0x14,
0x06, 0x0d, 0x2a, 0xfb, 0xcf, 0x8b, 0xa0, 0xaf, 0xb7, 0x10, 0x4f, 0x56, 0x42, 0x58, 0x79, 0xe4,
0xaa, 0xb6, 0xbb, 0xbe, 0xab, 0x2f, 0xd2, 0x94, 0x32, 0x85, 0x10, 0x9f, 0xb4, 0x98, 0xa3, 0xe7,
0xc5, 0x9e, 0xc3, 0xd7, 0xa7, 0x8c, 0x8e, 0xb6, 0x72, 0x3a, 0x2c, 0x5f, 0x15, 0x9c, 0x83, 0xd0,
0x74, 0x1d, 0x95, 0x30, 0x34, 0x25, 0x93, 0x57, 0xe5, 0xf1, 0x42, 0x31, 0xb7, 0x3a, 0x9a, 0x52,
0xe6, 0x4c, 0xa1, 0x0d, 0xa3, 0x21, 0x8d, 0xc3, 0xa4, 0x82, 0xe9, 0xc6, 0xb0, 0x87, 0xad, 0x71,
0xd8, 0xdd, 0x8e, 0x59, 0x04, 0x56, 0x37, 0xfc, 0x1b, 0x0e, 0x46, 0x21, 0xc8, 0x8e, 0x80, 0xf4,
0x8e, 0xc5, 0x09, 0x53, 0xb7, 0x8b, 0x50, 0x76, 0x3a, 0x71, 0xd0, 0x62, 0xc3, 0xc4, 0x3f, 0x4f,
0xc9, 0x48, 0x4e, 0x27, 0x08, 0xd4, 0x34, 0xf6, 0xe7, 0x46, 0x21, 0x53, 0x9a, 0x40, 0x0e, 0xcc,
0xab, 0x59, 0x56, 0xbe, 0x57, 0xb3, 0x94, 0x32, 0xfd, 0xae, 0x67, 0x91, 0x3a, 0x8c, 0xb6, 0x1b,
0x4e, 0x94, 0x2c, 0xbf, 0x9b, 0xc9, 0x30, 0x6d, 0x31, 0xe0, 0x83, 0xc3, 0xf9, 0x9f, 0x38, 0x9e,
0x3b, 0xc7, 0xe6, 0xea, 0xa2, 0xa8, 0xd3, 0xd4, 0xa2, 0x39, 0x0f, 0x14, 0xfc, 0x4d, 0x87, 0xae,
0x78, 0x44, 0x68, 0xfa, 0x71, 0x4b, 0xd4, 0xb3, 0x21, 0x8d, 0x3a, 0xcd, 0x58, 0xce, 0x86, 0x9b,
0x39, 0xae, 0x32, 0xc1, 0x58, 0x17, 0xb6, 0x89, 0xdf, 0x68, 0x08, 0x25, 0xef, 0x83, 0x72, 0x14,
0x3b, 0x61, 0xfc, 0x88, 0x65, 0x30, 0x6a, 0xd0, 0xb7, 0x13, 0x26, 0xa8, 0xf9, 0x91, 0x57, 0x00,
0x6a, 0x9e, 0xef, 0x45, 0x8d, 0x47, 0x3c, 0x15, 0xe4, 0x8a, 0x5f, 0x51, 0x1c, 0xd0, 0xe0, 0xc6,
0xac, 0x1b, 0x9f, 0xdb, 0x22, 0x8f, 0x59, 0xe2, 0xdb, 0x97, 0xb2, 0x6e, 0xa8, 0x30, 0x68, 0x50,
0xd9, 0x1f, 0x81, 0x33, 0xd9, 0x6b, 0xd1, 0x32, 0xc2, 0xab, 0x87, 0x41, 0xa7, 0x9d, 0x35, 0xdf,
0xfc, 0xda, 0x2c, 0x0a, 0x1c, 0x33, 0xdf, 0x7b, 0x9e, 0x5f, 0xcd, 0x9a, 0xef, 0x1b, 0x9e, 0x5f,
0x45, 0x8e, 0x39, 0xc6, 0x9d, 0xb5, 0xdf, 0xb7, 0xe0, 0xe2, 0x51, 0xb7, 0xb7, 0x59, 0xf4, 0x7e,
0xcf, 0x09, 0x7d, 0x79, 0x1d, 0x86, 0xdb, 0x8e, 0x3b, 0x4e, 0xe8, 0x23, 0x87, 0x92, 0x2e, 0x8c,
0x89, 0xd2, 0x3f, 0xe9, 0x90, 0xde, 0xcc, 0xf7, 0x2e, 0x39, 0x0b, 0x91, 0x54, 0xd2, 0x45, 0x94,
0x1d, 0xa2, 0x14, 0x68, 0x7f, 0xc7, 0x02, 0xb2, 0xb9, 0x4f, 0xc3, 0xd0, 0xab, 0x1a, 0xc5, 0x8a,
0xe4, 0x05, 0x98, 0xbc, 0xbb, 0xbd, 0xb9, 0xb1, 0x15, 0x78, 0x3e, 0xbf, 0x8f, 0x61, 0x94, 0xc8,
0x5c, 0x37, 0xe0, 0x98, 0xa2, 0x62, 0x41, 0xc6, 0xdd, 0xd7, 0xd8, 0x96, 0x73, 0xf9, 0xa0, 0x1d,
0xd2, 0x28, 0x52, 0x2f, 0x30, 0xc8, 0x20, 0xe3, 0xfa, 0xcd, 0x0c, 0x12, 0x7b, 0xe9, 0xc9, 0x26,
0x9c, 0x6b, 0xf1, 0x04, 0x5c, 0x95, 0xef, 0xb4, 0x91, 0xc8, 0xc6, 0x85, 0x49, 0xc1, 0xfb, 0x1b,
0xee, 0x1f, 0xce, 0x9f, 0x5b, 0xef, 0x47, 0x80, 0xfd, 0xdb, 0xd9, 0x5f, 0x2d, 0xc0, 0x84, 0xf1,
0x02, 0xc2, 0x31, 0x7c, 0x8a, 0xcc, 0xa3, 0x0d, 0x85, 0x63, 0x3e, 0xda, 0xf0, 0x1c, 0x94, 0xda,
0x41, 0xd3, 0x73, 0x3d, 0x55, 0x9d, 0x3f, 0xc9, 0xcf, 0xc0, 0x24, 0x0c, 0x15, 0x96, 0xdc, 0x83,
0xb2, 0xba, 0xca, 0x2c, 0xeb, 0xf5, 0xf2, 0xf2, 0xaa, 0xd4, 0xe2, 0xd5, 0x57, 0x94, 0xb5, 0x2c,
0x62, 0xc3, 0x18, 0x9f, 0xf9, 0x49, 0x86, 0x9f, 0x17, 0x80, 0xf0, 0x25, 0x11, 0xa1, 0xc4, 0xd8,
0xff, 0x34, 0x0a, 0x65, 0xa4, 0xed, 0x60, 0x39, 0xa4, 0xd5, 0x88, 0xbc, 0x11, 0x8a, 0x9d, 0xb0,
0x29, 0x07, 0x4b, 0xa5, 0x7f, 0x6e, 0xe1, 0x1a, 0x32, 0x78, 0x6a, 0xbb, 0x29, 0x9c, 0xe8, 0xa4,
0xb0, 0x78, 0xe4, 0x49, 0xe1, 0x4b, 0x30, 0x15, 0x45, 0x8d, 0xad, 0xd0, 0xdb, 0x77, 0x62, 0x36,
0x89, 0x65, 0xae, 0x44, 0x1f, 0xcd, 0x6c, 0x5f, 0xd3, 0x48, 0x4c, 0xd3, 0x92, 0xab, 0x30, 0xa3,
0xcf, 0xeb, 0x68, 0x18, 0xf3, 0xd4, 0x88, 0xc8, 0xa2, 0xa8, 0x93, 0x11, 0x7d, 0xc2, 0x27, 0x09,
0xb0, 0xb7, 0x0d, 0x59, 0x81, 0xd3, 0x29, 0x20, 0x53, 0x44, 0xa4, 0x58, 0x54, 0x2d, 0x40, 0x8a,
0x0f, 0xd3, 0xa5, 0xa7, 0x05, 0x59, 0x87, 0x33, 0xe2, 0xfb, 0xf2, 0x2b, 0xf0, 0xaa, 0x47, 0xe3,
0x9c, 0xd1, 0xff, 0x91, 0x8c, 0xce, 0x5c, 0xed, 0x25, 0xc1, 0x7e, 0xed, 0xd8, 0x0c, 0x55, 0xe0,
0xd5, 0x15, 0x69, 0x29, 0xd5, 0x0c, 0x55, 0x6c, 0x56, 0xab, 0x68, 0xd2, 0x91, 0xf7, 0xc2, 0xd3,
0xfa, 0xa7, 0xc8, 0xac, 0x09, 0xf7, 0x61, 0x45, 0x96, 0x42, 0xa8, 0x9b, 0x46, 0x57, 0xfb, 0x92,
0x55, 0x71, 0x50, 0x7b, 0xb2, 0x0b, 0x73, 0x0a, 0x75, 0x99, 0x99, 0x83, 0x76, 0xe8, 0x45, 0xb4,
0xe2, 0x44, 0xf4, 0x56, 0xd8, 0xe4, 0xc5, 0x13, 0x65, 0xfd, 0x8c, 0xc3, 0x55, 0x2f, 0xbe, 0xd6,
0x8f, 0x12, 0xd7, 0xf0, 0x21, 0x5c, 0x98, 0xb7, 0x42, 0x7d, 0x67, 0xb7, 0x49, 0x37, 0x97, 0x57,
0x79, 0x49, 0x85, 0xe1, 0xad, 0x5c, 0x4e, 0x10, 0xa8, 0x69, 0x94, 0x7b, 0x3e, 0x39, 0xd0, 0x3d,
0xff, 0xb6, 0x05, 0x53, 0x6a, 0xb2, 0x3f, 0x81, 0x3c, 0x58, 0x33, 0x9d, 0x07, 0xbb, 0x3a, 0xac,
0x9b, 0x28, 0x35, 0x1f, 0x10, 0x4c, 0x7d, 0xaf, 0x0c, 0xc0, 0x1f, 0xc6, 0xf1, 0x78, 0xa9, 0xee,
0x45, 0x18, 0x09, 0x69, 0x3b, 0xc8, 0x5a, 0x3e, 0x9e, 0xc3, 0xe7, 0x98, 0x1f, 0xdc, 0xe5, 0xdc,
0xef, 0xe4, 0x78, 0xf4, 0x7f, 0xf6, 0xe4, 0x78, 0x1b, 0xce, 0x79, 0x7e, 0x44, 0xdd, 0x4e, 0x28,
0x77, 0xce, 0x6b, 0x41, 0xa4, 0xac, 0x43, 0xa9, 0xf2, 0x46, 0xc9, 0xe8, 0xdc, 0x6a, 0x3f, 0x22,
0xec, 0xdf, 0x96, 0x0d, 0x69, 0x82, 0x90, 0x77, 0x82, 0x74, 0x88, 0x2f, 0xe1, 0xa8, 0x28, 0xf4,
0x82, 0x58, 0xab, 0x25, 0x97, 0x7e, 0x32, 0x0b, 0x62, 0xed, 0xca, 0x36, 0x6a, 0x9a, 0xfe, 0x56,
0xb1, 0x9c, 0x93, 0x55, 0x84, 0x13, 0x5b, 0xc5, 0x64, 0x7d, 0x4e, 0x0c, 0x7c, 0x46, 0x21, 0xd9,
0xac, 0x27, 0x07, 0x6e, 0xd6, 0x2f, 0xc3, 0xb4, 0xe7, 0x37, 0x68, 0xe8, 0xc5, 0xb4, 0xca, 0xd7,
0xc2, 0xec, 0x14, 0x1f, 0x08, 0x95, 0x7d, 0x5a, 0x4d, 0x61, 0x31, 0x43, 0x9d, 0x36, 0x2a, 0xd3,
0xc7, 0x30, 0x2a, 0x03, 0x4c, 0xf9, 0xa9, 0x7c, 0x4c, 0xf9, 0xe9, 0xe1, 0x4d, 0xf9, 0xcc, 0x63,
0x35, 0xe5, 0x24, 0x17, 0x53, 0xfe, 0x2c, 0x8c, 0xb6, 0xc3, 0xe0, 0xa0, 0x3b, 0x7b, 0x26, 0xed,
0x9e, 0x6f, 0x31, 0x20, 0x0a, 0x9c, 0x59, 0x40, 0x77, 0xf6, 0xe1, 0x05, 0x74, 0xf6, 0xeb, 0x05,
0x38, 0xa7, 0x2d, 0x1d, 0x9b, 0x5f, 0x5e, 0x8d, 0xad, 0x75, 0x7e, 0x33, 0x53, 0x14, 0x6d, 0x18,
0x89, 0x4f, 0x9d, 0x43, 0x55, 0x18, 0x34, 0xa8, 0x78, 0xfe, 0x90, 0x86, 0xbc, 0xec, 0x37, 0x6b,
0x06, 0x97, 0x25, 0x1c, 0x15, 0x05, 0x7f, 0x55, 0x8f, 0x86, 0xb1, 0x3c, 0x93, 0xc9, 0x56, 0x34,
0x2d, 0x6b, 0x14, 0x9a, 0x74, 0xcc, 0x5d, 0x74, 0x93, 0x25, 0xc8, 0x4c, 0xe1, 0xa4, 0x70, 0x17,
0xd5, 0xaa, 0x53, 0xd8, 0x44, 0x1d, 0x9e, 0x28, 0x1e, 0xed, 0x55, 0x87, 0x67, 0x21, 0x14, 0x85,
0xfd, 0x1f, 0x16, 0xbc, 0xa1, 0xef, 0x50, 0x3c, 0x81, 0xed, 0xed, 0x20, 0xbd, 0xbd, 0x6d, 0x0f,
0xbf, 0xbd, 0xf5, 0xf4, 0x62, 0xc0, 0x56, 0xf7, 0x97, 0x16, 0x4c, 0x6b, 0xfa, 0x27, 0xd0, 0x55,
0x2f, 0xd7, 0xf7, 0xf1, 0xb4, 0xea, 0xa2, 0x1c, 0x35, 0xd5, 0xb7, 0x6f, 0xf3, 0xbe, 0x89, 0x60,
0x6e, 0xc9, 0x4d, 0x1e, 0xa0, 0x39, 0x22, 0x88, 0xe9, 0xc2, 0x18, 0xbf, 0xc2, 0x1f, 0xe5, 0x13,
0x54, 0xa6, 0xe5, 0xf3, 0x13, 0x20, 0x1d, 0x54, 0xf2, 0x9f, 0x11, 0x4a, 0x81, 0xbc, 0x28, 0xdd,
0x8b, 0x98, 0xbd, 0xac, 0xca, 0x94, 0xab, 0x2e, 0x4a, 0x97, 0x70, 0x54, 0x14, 0x76, 0x0b, 0x66,
0xd3, 0xcc, 0x57, 0x68, 0x8d, 0xe7, 0xee, 0x8e, 0xd5, 0xcd, 0x45, 0x28, 0x3b, 0xbc, 0xd5, 0x5a,
0xc7, 0xc9, 0xbe, 0x42, 0xb3, 0x94, 0x20, 0x50, 0xd3, 0xd8, 0xbf, 0x61, 0xc1, 0x99, 0x3e, 0x9d,
0xc9, 0x31, 0xd5, 0x1c, 0x6b, 0x2b, 0x30, 0xe0, 0x65, 0xa0, 0x2a, 0xad, 0x39, 0x49, 0x76, 0xc8,
0xb0, 0x6a, 0x2b, 0x02, 0x8c, 0x09, 0xde, 0xfe, 0x67, 0x0b, 0x4e, 0xa5, 0x75, 0x8d, 0xc8, 0x75,
0x20, 0xa2, 0x33, 0x2b, 0x5e, 0xe4, 0x06, 0xfb, 0x34, 0xec, 0xb2, 0x9e, 0x0b, 0xad, 0xe7, 0x24,
0x27, 0xb2, 0xd4, 0x43, 0x81, 0x7d, 0x5a, 0xf1, 0xda, 0xdf, 0xaa, 0x1a, 0xed, 0x64, 0xa6, 0xdc,
0xce, 0x73, 0xa6, 0xe8, 0x8f, 0x69, 0x46, 0xd0, 0x4a, 0x24, 0x9a, 0xf2, 0xed, 0xef, 0x8c, 0x80,
0x3a, 0x8b, 0xe2, 0x79, 0x88, 0x9c, 0xb2, 0x38, 0xa9, 0xa7, 0x8a, 0x8a, 0x27, 0x78, 0xaa, 0x68,
0xe4, 0x61, 0x39, 0x02, 0xf1, 0x6e, 0x8e, 0xf6, 0x45, 0x0d, 0xa3, 0xbf, 0xa3, 0x51, 0x68, 0xd2,
0x31, 0x4d, 0x9a, 0xde, 0x3e, 0x15, 0x8d, 0xc6, 0xd2, 0x9a, 0xac, 0x25, 0x08, 0xd4, 0x34, 0x4c,
0x93, 0xaa, 0x57, 0xab, 0xc9, 0x48, 0x51, 0x69, 0xc2, 0x46, 0x07, 0x39, 0x86, 0x51, 0x34, 0x82,
0x60, 0x4f, 0xfa, 0x7f, 0x8a, 0xe2, 0x5a, 0x10, 0xec, 0x21, 0xc7, 0x30, 0x8f, 0xc5, 0x0f, 0xc2,
0x96, 0xd3, 0xf4, 0x3e, 0x48, 0xab, 0x4a, 0x8a, 0xf4, 0xfb, 0x94, 0xc7, 0xb2, 0xd1, 0x4b, 0x82,
0xfd, 0xda, 0xb1, 0x19, 0xd8, 0x0e, 0x69, 0xd5, 0x73, 0x63, 0x93, 0x1b, 0xa4, 0x67, 0xe0, 0x56,
0x0f, 0x05, 0xf6, 0x69, 0x45, 0x96, 0xe0, 0x54, 0x72, 0x96, 0x98, 0xd4, 0x90, 0x08, 0x67, 0x50,
0xf9, 0xe1, 0x98, 0x46, 0x63, 0x96, 0x9e, 0x59, 0x9b, 0x96, 0xac, 0xe4, 0xe1, 0x6e, 0xa2, 0x61,
0x6d, 0x92, 0x0a, 0x1f, 0x54, 0x14, 0xf6, 0xc7, 0x8b, 0x6c, 0x77, 0x1c, 0x70, 0x3b, 0xf7, 0x89,
0x65, 0x0d, 0xd3, 0x33, 0x72, 0xe4, 0x18, 0x33, 0xf2, 0x05, 0x98, 0xbc, 0x1b, 0x05, 0xbe, 0xca,
0xc8, 0x8d, 0x0e, 0xcc, 0xc8, 0x19, 0x54, 0xfd, 0x33, 0x72, 0x63, 0x79, 0x65, 0xe4, 0xc6, 0x1f,
0x31, 0x23, 0xf7, 0x27, 0xa3, 0x70, 0x5e, 0x9d, 0x27, 0xd3, 0xf8, 0x5e, 0x10, 0xee, 0x79, 0x7e,
0x9d, 0x9f, 0xc1, 0x7e, 0xc5, 0x82, 0x49, 0xb1, 0x5e, 0xe4, 0xc3, 0x08, 0xe2, 0xcc, 0xb1, 0x96,
0xd3, 0xdd, 0xb5, 0x94, 0xb0, 0x85, 0x1d, 0x43, 0x50, 0xe6, 0x95, 0x0a, 0x13, 0x85, 0x29, 0x8d,
0xc8, 0x87, 0x01, 0x92, 0x17, 0xb3, 0x6a, 0x39, 0xbd, 0x1b, 0x96, 0xe8, 0x87, 0xb4, 0xa6, 0x7d,
0xd3, 0x1d, 0x25, 0x04, 0x0d, 0x81, 0xe4, 0x75, 0x4b, 0xdd, 0x15, 0x11, 0xa7, 0x59, 0xaf, 0x3e,
0x96, 0xb1, 0x39, 0xce, 0xd5, 0x11, 0x84, 0x71, 0xcf, 0xaf, 0xb3, 0x79, 0x22, 0x93, 0x98, 0x6f,
0xe9, 0x57, 0xbf, 0xb0, 0x16, 0x38, 0xd5, 0x8a, 0xd3, 0x74, 0x7c, 0x97, 0x86, 0xab, 0x82, 0xdc,
0x7c, 0x36, 0x89, 0x03, 0x30, 0x61, 0xd4, 0x73, 0x39, 0x73, 0xf4, 0x38, 0x97, 0x33, 0xe7, 0xde,
0x03, 0x33, 0x3d, 0x1f, 0xf3, 0x44, 0x57, 0x47, 0x1e, 0xfd, 0xd6, 0x89, 0xfd, 0x07, 0x63, 0x7a,
0xd3, 0xda, 0x08, 0xaa, 0xe2, 0x8a, 0x60, 0xa8, 0xbf, 0xa8, 0xf4, 0x3d, 0x73, 0x9c, 0x22, 0xc6,
0xd3, 0x4b, 0x0a, 0x88, 0xa6, 0x48, 0x36, 0x47, 0xdb, 0x4e, 0x48, 0xfd, 0xc7, 0x3d, 0x47, 0xb7,
0x94, 0x10, 0x34, 0x04, 0x92, 0x46, 0xea, 0xb8, 0xf5, 0xca, 0xf0, 0xc7, 0xad, 0xcc, 0x1d, 0xee,
0x7b, 0x95, 0xeb, 0xf3, 0x16, 0x4c, 0xfb, 0xa9, 0x99, 0x2b, 0x8f, 0xdc, 0x76, 0x1e, 0xc7, 0xaa,
0x10, 0x57, 0xb3, 0xd3, 0x30, 0xcc, 0xc8, 0xef, 0xb7, 0xa5, 0x8d, 0x9e, 0x70, 0x4b, 0xd3, 0x77,
0x8d, 0xc7, 0x06, 0xdd, 0x35, 0x26, 0xbe, 0x7a, 0x65, 0x60, 0x3c, 0xf7, 0x57, 0x06, 0xa0, 0xcf,
0x0b, 0x03, 0x77, 0xa0, 0xec, 0x86, 0xd4, 0x89, 0x1f, 0xf1, 0xc2, 0x39, 0x7f, 0xec, 0x6e, 0x39,
0x61, 0x80, 0x9a, 0x97, 0xfd, 0x67, 0x45, 0x38, 0x9d, 0x8c, 0x48, 0x72, 0x14, 0xc5, 0xf6, 0x47,
0x21, 0x57, 0x3b, 0xb7, 0x6a, 0x7f, 0xbc, 0x96, 0x20, 0x50, 0xd3, 0x30, 0x7f, 0xac, 0x13, 0xd1,
0xcd, 0x36, 0xf5, 0xd7, 0xbc, 0xdd, 0x88, 0x8f, 0xb8, 0x51, 0x42, 0x76, 0x4b, 0xa3, 0xd0, 0xa4,
0x63, 0xce, 0xb8, 0xf0, 0x8b, 0xa3, 0xec, 0xc9, 0xae, 0xf4, 0xb7, 0x31, 0xc1, 0x93, 0x2f, 0xf7,
0x7d, 0x2e, 0x24, 0x9f, 0x9a, 0x86, 0x9e, 0x13, 0xb8, 0x13, 0xbe, 0x13, 0xf2, 0x39, 0x0b, 0x4e,
0xed, 0xa5, 0xea, 0x57, 0x12, 0x93, 0x3c, 0x64, 0xa5, 0x65, 0xba, 0x28, 0x46, 0x4f, 0xe1, 0x34,
0x3c, 0xc2, 0xac, 0x74, 0xfb, 0xdf, 0x2c, 0x30, 0xcd, 0xd3, 0xf1, 0x3c, 0x2b, 0xe3, 0x01, 0xa8,
0xc2, 0x11, 0x0f, 0x40, 0x25, 0x4e, 0x58, 0xf1, 0x78, 0x4e, 0xff, 0xc8, 0x09, 0x9c, 0xfe, 0xd1,
0x81, 0x5e, 0xdb, 0x1b, 0xa1, 0xd8, 0xf1, 0xaa, 0xd2, 0x6f, 0xd7, 0x87, 0x61, 0xab, 0x2b, 0xc8,
0xe0, 0xf6, 0xef, 0x8d, 0xea, 0x38, 0x5d, 0x1e, 0xc5, 0xff, 0x50, 0x74, 0xbb, 0xa6, 0x0a, 0x67,
0x45, 0xcf, 0x37, 0x7a, 0x0a, 0x67, 0xdf, 0x7d, 0xf2, 0x4a, 0x0b, 0x31, 0x40, 0x83, 0xea, 0x66,
0xc7, 0x8f, 0x28, 0xb3, 0xb8, 0x0b, 0x25, 0x16, 0xda, 0xf0, 0x84, 0x5b, 0x29, 0xa5, 0x54, 0xe9,
0x9a, 0x84, 0x3f, 0x38, 0x9c, 0x7f, 0xd7, 0xc9, 0xd5, 0x4a, 0x5a, 0xa3, 0xe2, 0x4f, 0x22, 0x28,
0xb3, 0xbf, 0x79, 0x45, 0x88, 0x0c, 0x9a, 0x6e, 0x29, 0x5b, 0x94, 0x20, 0x72, 0x29, 0x37, 0xd1,
0x72, 0x88, 0x0f, 0x65, 0xfe, 0x54, 0x11, 0x17, 0x2a, 0x62, 0xab, 0x2d, 0x55, 0x97, 0x91, 0x20,
0x1e, 0x1c, 0xce, 0xbf, 0x74, 0x72, 0xa1, 0xaa, 0x39, 0x6a, 0x11, 0xf6, 0xdf, 0x15, 0xf5, 0xdc,
0x95, 0xf5, 0xd2, 0x3f, 0x14, 0x73, 0xf7, 0xc5, 0xcc, 0xdc, 0xbd, 0xd8, 0x33, 0x77, 0xa7, 0xf5,
0x73, 0x3e, 0xa9, 0xd9, 0xf8, 0xa4, 0x37, 0xd8, 0xa3, 0xe3, 0x78, 0xee, 0x59, 0xbc, 0xd6, 0xf1,
0x42, 0x1a, 0x6d, 0x85, 0x1d, 0xdf, 0xf3, 0xeb, 0xf2, 0x51, 0x47, 0xc3, 0xb3, 0x48, 0xa1, 0x31,
0x4b, 0x6f, 0x7f, 0x95, 0x9f, 0x77, 0x1a, 0xc5, 0x65, 0xec, 0x2b, 0x37, 0xf9, 0x6b, 0x4f, 0xa2,
0xa2, 0x54, 0x7d, 0x65, 0xf1, 0xc4, 0x93, 0xc0, 0x91, 0x7b, 0x30, 0xbe, 0x2b, 0x5e, 0x9c, 0xc8,
0xe7, 0x8a, 0x93, 0x7c, 0xbe, 0x82, 0x5f, 0x26, 0x4d, 0xde, 0xb2, 0x78, 0xa0, 0xff, 0xc4, 0x44,
0x9a, 0xfd, 0x4b, 0x45, 0x38, 0x95, 0x79, 0x8b, 0x88, 0x05, 0xfc, 0xc9, 0xc3, 0x53, 0xd9, 0xec,
0xbc, 0x7a, 0xd4, 0x58, 0x51, 0x90, 0x0f, 0x00, 0x54, 0x69, 0xbb, 0x19, 0x74, 0xb9, 0xe3, 0x32,
0x72, 0x62, 0xc7, 0x45, 0xf9, 0xba, 0x2b, 0x8a, 0x0b, 0x1a, 0x1c, 0x65, 0x19, 0xed, 0xa8, 0x78,
0x4f, 0x23, 0x5d, 0x46, 0x6b, 0xdc, 0xf4, 0x1b, 0x7b, 0xb2, 0x37, 0xfd, 0x3c, 0x38, 0x25, 0x54,
0x54, 0x25, 0x5c, 0x8f, 0x50, 0xa9, 0x75, 0x86, 0xcd, 0xa8, 0x95, 0x34, 0x1b, 0xcc, 0xf2, 0xb5,
0x3f, 0x5b, 0x60, 0xee, 0x9b, 0x18, 0xec, 0xf5, 0x24, 0x39, 0xfe, 0x66, 0x18, 0x73, 0x3a, 0x71,
0x23, 0xe8, 0x79, 0x01, 0x64, 0x89, 0x43, 0x51, 0x62, 0xc9, 0x1a, 0x8c, 0x54, 0x9d, 0x38, 0x79,
0x94, 0xff, 0x24, 0xca, 0xe9, 0x4c, 0x98, 0x13, 0x53, 0xe4, 0x5c, 0xc8, 0x33, 0x30, 0x12, 0x3b,
0xf5, 0xd4, 0x0b, 0x9e, 0x3b, 0x4e, 0x3d, 0x42, 0x0e, 0x35, 0x77, 0x97, 0x91, 0x23, 0x76, 0x97,
0x97, 0x8c, 0x7f, 0x17, 0x61, 0x9c, 0xba, 0xf4, 0xfe, 0x8b, 0x07, 0x51, 0xd8, 0x9f, 0xa2, 0xb5,
0x7f, 0x04, 0x26, 0xcd, 0x7f, 0x01, 0x71, 0xac, 0xbb, 0x46, 0xf6, 0x3f, 0x8e, 0xc0, 0x54, 0xaa,
0xcc, 0x2f, 0x35, 0xcb, 0xad, 0x23, 0x67, 0x39, 0x3f, 0x4f, 0xeb, 0xf8, 0x54, 0x16, 0x71, 0x1a,
0xe7, 0x69, 0x1d, 0x9f, 0xa2, 0xc0, 0xb1, 0xaf, 0x52, 0x0d, 0xbb, 0xd8, 0xf1, 0x65, 0x56, 0x5e,
0x7d, 0x95, 0x15, 0x0e, 0x45, 0x89, 0x65, 0x01, 0xec, 0x64, 0xc4, 0x8d, 0xa2, 0xb0, 0x11, 0x72,
0xd5, 0x5c, 0xcf, 0xe3, 0xd5, 0x34, 0x59, 0xd2, 0xca, 0x03, 0x7a, 0x13, 0x82, 0x29, 0x89, 0xe4,
0x13, 0x96, 0xf9, 0x5e, 0xdc, 0x58, 0x1e, 0xa7, 0x49, 0xd9, 0x2a, 0x4a, 0xb1, 0x82, 0x1e, 0xfe,
0x6c, 0x5c, 0xa4, 0x16, 0xf0, 0xf8, 0xe3, 0x59, 0xc0, 0xd0, 0x67, 0xf1, 0xbe, 0x15, 0xca, 0x2d,
0xc7, 0xf7, 0x6a, 0x34, 0x8a, 0xc5, 0xbf, 0x6f, 0x29, 0x8b, 0xe8, 0x69, 0x3d, 0x01, 0xa2, 0xc6,
0xf3, 0x7f, 0x92, 0xc4, 0x3b, 0x26, 0x82, 0x98, 0xb2, 0xf1, 0x4f, 0x92, 0x34, 0x18, 0x4d, 0x1a,
0xfb, 0xb7, 0x2d, 0x38, 0xd7, 0x77, 0x30, 0x7e, 0x70, 0xd3, 0x9f, 0xf6, 0xef, 0x14, 0xe0, 0x4c,
0x9f, 0x32, 0x58, 0xd2, 0x7d, 0x6c, 0xcf, 0x0a, 0xca, 0x3a, 0xdb, 0xa9, 0x81, 0x73, 0xe3, 0x64,
0xdb, 0x90, 0xde, 0x0a, 0x8a, 0x4f, 0x74, 0x2b, 0xb0, 0xbf, 0x5a, 0x00, 0xe3, 0x01, 0x4c, 0xf2,
0x11, 0xb3, 0xe2, 0xdb, 0xca, 0xab, 0x3a, 0x59, 0x30, 0x57, 0x15, 0xe3, 0x62, 0xd4, 0xfa, 0x15,
0x90, 0x67, 0xe7, 0x6b, 0xe1, 0xe8, 0xf9, 0x4a, 0x9a, 0x49, 0x69, 0x7d, 0x31, 0xff, 0xd2, 0xfa,
0x72, 0x4f, 0x59, 0xfd, 0x2f, 0x58, 0x62, 0xa6, 0x65, 0xba, 0xa4, 0x2d, 0xac, 0xf5, 0x10, 0x0b,
0xfb, 0x36, 0x28, 0x45, 0xb4, 0x59, 0x63, 0x9e, 0x9d, 0xb4, 0xc4, 0xfa, 0xbd, 0x6d, 0x09, 0x47,
0x45, 0xc1, 0xef, 0xce, 0x36, 0x9b, 0xc1, 0xbd, 0xcb, 0xad, 0x76, 0xdc, 0x95, 0x36, 0x59, 0xdf,
0x9d, 0x55, 0x18, 0x34, 0xa8, 0xec, 0x7f, 0xb7, 0xc4, 0xe7, 0x94, 0x3e, 0xfa, 0x8b, 0x99, 0x3b,
0x8d, 0xc7, 0x77, 0x6f, 0x7f, 0x06, 0xc0, 0x55, 0x6f, 0x12, 0xe4, 0xf3, 0x2e, 0xa6, 0x7e, 0xe3,
0xc0, 0x7c, 0xac, 0x31, 0x81, 0xa1, 0x21, 0x2f, 0xb5, 0x78, 0x8a, 0x47, 0x2d, 0x1e, 0xfb, 0x5f,
0x2c, 0x48, 0x6d, 0x16, 0xa4, 0x0d, 0xa3, 0x4c, 0x83, 0x6e, 0x3e, 0x2f, 0x28, 0x98, 0xac, 0xd9,
0xc2, 0x92, 0xd3, 0x82, 0xff, 0x89, 0x42, 0x10, 0x69, 0x4a, 0xef, 0xbc, 0x90, 0xc7, 0x2b, 0x1f,
0xa6, 0x40, 0xe6, 0xdf, 0xcb, 0x7f, 0x88, 0xa1, 0x3c, 0x7d, 0xfb, 0x45, 0x98, 0xe9, 0x51, 0x8a,
0xdf, 0x48, 0x0a, 0x92, 0x67, 0x23, 0x8c, 0x19, 0xc8, 0xef, 0x47, 0xa2, 0xc0, 0x31, 0x07, 0xff,
0x74, 0x96, 0x3d, 0xf9, 0x92, 0x05, 0x33, 0x51, 0x96, 0xdf, 0xe3, 0x1a, 0x3b, 0x95, 0xb9, 0xea,
0x41, 0x61, 0xaf, 0x12, 0xf6, 0x7f, 0x49, 0xf3, 0x24, 0xfe, 0x81, 0x98, 0xda, 0x5c, 0xac, 0x81,
0x9b, 0x0b, 0x5b, 0x62, 0x6e, 0x83, 0x56, 0x3b, 0xcd, 0x9e, 0xda, 0x9c, 0x6d, 0x09, 0x47, 0x45,
0x91, 0x7a, 0x1f, 0xaf, 0x78, 0xe4, 0xfb, 0x78, 0x2f, 0xc0, 0xa4, 0xf9, 0x34, 0x0a, 0x4f, 0xa1,
0xc9, 0xc3, 0x07, 0xf3, 0x15, 0x15, 0x4c, 0x51, 0x65, 0xde, 0x57, 0x1b, 0x3d, 0xf2, 0x7d, 0xb5,
0xe7, 0xa0, 0x24, 0xdf, 0x0a, 0x4b, 0xf2, 0xbb, 0xa2, 0xf0, 0x47, 0xc2, 0x50, 0x61, 0x99, 0x81,
0x68, 0x39, 0x7e, 0xc7, 0x69, 0xb2, 0x11, 0x92, 0xf5, 0x80, 0x6a, 0x65, 0xad, 0x2b, 0x0c, 0x1a,
0x54, 0xac, 0xc7, 0xb1, 0xd7, 0xa2, 0xaf, 0x04, 0x7e, 0x92, 0x19, 0x51, 0x3d, 0xde, 0x91, 0x70,
0x54, 0x14, 0xf6, 0x3f, 0x58, 0x90, 0x7d, 0xe8, 0x28, 0x55, 0x83, 0x68, 0x1d, 0x59, 0x83, 0x98,
0xae, 0xaf, 0x2a, 0x1c, 0xab, 0xbe, 0xca, 0x2c, 0x7d, 0x2a, 0x3e, 0xb4, 0xf4, 0xe9, 0x4d, 0xfa,
0x5e, 0xbb, 0xa8, 0x91, 0x9a, 0xe8, 0x77, 0xa7, 0x9d, 0xd8, 0x30, 0xe6, 0x3a, 0xaa, 0xc4, 0x7b,
0x52, 0xb8, 0x55, 0xcb, 0x4b, 0x9c, 0x48, 0x62, 0x2a, 0xbb, 0x5f, 0xff, 0xee, 0x85, 0xa7, 0xbe,
0xf1, 0xdd, 0x0b, 0x4f, 0x7d, 0xeb, 0xbb, 0x17, 0x9e, 0xfa, 0xd8, 0xfd, 0x0b, 0xd6, 0xd7, 0xef,
0x5f, 0xb0, 0xbe, 0x71, 0xff, 0x82, 0xf5, 0xad, 0xfb, 0x17, 0xac, 0xef, 0xdc, 0xbf, 0x60, 0x7d,
0xfe, 0x6f, 0x2f, 0x3c, 0xf5, 0xca, 0xbb, 0x87, 0xf9, 0x8f, 0xb5, 0xff, 0x1d, 0x00, 0x00, 0xff,
0xff, 0xd1, 0x9f, 0xce, 0xf7, 0xf0, 0x76, 0x00, 0x00,
// 6831 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x59, 0x6c, 0x24, 0xc9,
0x71, 0xe8, 0x56, 0x37, 0x8f, 0xee, 0xe0, 0x31, 0x64, 0xce, 0xb1, 0x14, 0xdf, 0x6a, 0x38, 0xa8,
0x85, 0xa4, 0x7d, 0x4f, 0x12, 0xf9, 0x76, 0xde, 0x4a, 0x6f, 0xad, 0x95, 0x57, 0x62, 0x93, 0x73,
0x70, 0x86, 0x1c, 0x72, 0x82, 0x9c, 0x19, 0x6b, 0x25, 0xcb, 0x5b, 0xac, 0xce, 0xee, 0xae, 0x61,
0x77, 0x55, 0x6f, 0x55, 0x35, 0x87, 0x2d, 0x59, 0x17, 0x20, 0x5b, 0x0b, 0xe8, 0x84, 0xe4, 0x0f,
0x09, 0x30, 0x6c, 0xf9, 0x80, 0x01, 0x7f, 0x08, 0xb6, 0x7f, 0x7c, 0xc0, 0xf0, 0x87, 0xe5, 0x1f,
0x19, 0xfe, 0xb0, 0x00, 0x1b, 0x96, 0x6c, 0xc1, 0xb4, 0x34, 0xb6, 0x21, 0xdb, 0x80, 0x6d, 0xf8,
0xf8, 0xf1, 0xc0, 0x1f, 0x46, 0x1e, 0x95, 0x99, 0x55, 0xdd, 0x3d, 0x24, 0xa7, 0x6b, 0xc6, 0x82,
0xe0, 0x3f, 0x76, 0x44, 0x64, 0x44, 0x64, 0x56, 0x66, 0x64, 0x44, 0x64, 0x64, 0x12, 0xd6, 0xeb,
0x5e, 0xdc, 0xe8, 0xec, 0x2e, 0xba, 0x41, 0x6b, 0xc9, 0x09, 0xeb, 0x41, 0x3b, 0x0c, 0xee, 0xf2,
0x3f, 0xde, 0xee, 0x56, 0x97, 0xf6, 0x2f, 0x2e, 0xb5, 0xf7, 0xea, 0x4b, 0x4e, 0xdb, 0x8b, 0x96,
0x9c, 0x76, 0xbb, 0xe9, 0xb9, 0x4e, 0xec, 0x05, 0xfe, 0xd2, 0xfe, 0xf3, 0x4e, 0xb3, 0xdd, 0x70,
0x9e, 0x5f, 0xaa, 0x53, 0x9f, 0x86, 0x4e, 0x4c, 0xab, 0x8b, 0xed, 0x30, 0x88, 0x03, 0xf2, 0x6e,
0xcd, 0x6d, 0x31, 0xe1, 0xc6, 0xff, 0xf8, 0x09, 0xb7, 0xba, 0xb8, 0x7f, 0x71, 0xb1, 0xbd, 0x57,
0x5f, 0x64, 0xdc, 0x16, 0x0d, 0x6e, 0x8b, 0x09, 0xb7, 0xf9, 0xb7, 0x1b, 0xba, 0xd4, 0x83, 0x7a,
0xb0, 0xc4, 0x99, 0xee, 0x76, 0x6a, 0xfc, 0x17, 0xff, 0xc1, 0xff, 0x12, 0xc2, 0xe6, 0xed, 0xbd,
0x17, 0xa3, 0x45, 0x2f, 0x60, 0xea, 0x2d, 0xb9, 0x41, 0x48, 0x97, 0xf6, 0x7b, 0x14, 0x9a, 0x7f,
0x41, 0xd3, 0xb4, 0x1c, 0xb7, 0xe1, 0xf9, 0x34, 0xec, 0xea, 0x3e, 0xb5, 0x68, 0xec, 0xf4, 0x6b,
0xb5, 0x34, 0xa8, 0x55, 0xd8, 0xf1, 0x63, 0xaf, 0x45, 0x7b, 0x1a, 0xbc, 0xf3, 0xa8, 0x06, 0x91,
0xdb, 0xa0, 0x2d, 0x27, 0xdb, 0xce, 0x7e, 0x0d, 0xa6, 0x96, 0xef, 0x6c, 0x2f, 0x77, 0xe2, 0xc6,
0x4a, 0xe0, 0xd7, 0xbc, 0x3a, 0x79, 0x07, 0x4c, 0xb8, 0xcd, 0x4e, 0x14, 0xd3, 0xf0, 0x86, 0xd3,
0xa2, 0x73, 0xd6, 0x05, 0xeb, 0xb9, 0x72, 0xe5, 0xf4, 0x37, 0x0e, 0x17, 0x9e, 0xba, 0x7f, 0xb8,
0x30, 0xb1, 0xa2, 0x51, 0x68, 0xd2, 0x91, 0xff, 0x0d, 0xe3, 0x61, 0xd0, 0xa4, 0xcb, 0x78, 0x63,
0xae, 0xc0, 0x9b, 0x9c, 0x92, 0x4d, 0xc6, 0x51, 0x80, 0x31, 0xc1, 0xdb, 0x7f, 0x56, 0x00, 0x58,
0x6e, 0xb7, 0xb7, 0xc2, 0xe0, 0x2e, 0x75, 0x63, 0xf2, 0x2a, 0x94, 0xd8, 0x28, 0x54, 0x9d, 0xd8,
0xe1, 0xd2, 0x26, 0x2e, 0xfe, 0xdf, 0x45, 0xd1, 0x99, 0x45, 0xb3, 0x33, 0xfa, 0xcb, 0x31, 0xea,
0xc5, 0xfd, 0xe7, 0x17, 0x37, 0x77, 0x59, 0xfb, 0x0d, 0x1a, 0x3b, 0x15, 0x22, 0x85, 0x81, 0x86,
0xa1, 0xe2, 0x4a, 0x7c, 0x18, 0x89, 0xda, 0xd4, 0xe5, 0x8a, 0x4d, 0x5c, 0x5c, 0x5f, 0x1c, 0x66,
0x8a, 0x2c, 0x6a, 0xcd, 0xb7, 0xdb, 0xd4, 0xad, 0x4c, 0x4a, 0xc9, 0x23, 0xec, 0x17, 0x72, 0x39,
0x64, 0x1f, 0xc6, 0xa2, 0xd8, 0x89, 0x3b, 0xd1, 0x5c, 0x91, 0x4b, 0xbc, 0x91, 0x9b, 0x44, 0xce,
0xb5, 0x32, 0x2d, 0x65, 0x8e, 0x89, 0xdf, 0x28, 0xa5, 0xd9, 0x7f, 0x69, 0xc1, 0xb4, 0x26, 0x5e,
0xf7, 0xa2, 0x98, 0x7c, 0xa0, 0x67, 0x70, 0x17, 0x8f, 0x37, 0xb8, 0xac, 0x35, 0x1f, 0xda, 0x19,
0x29, 0xac, 0x94, 0x40, 0x8c, 0x81, 0x6d, 0xc1, 0xa8, 0x17, 0xd3, 0x56, 0x34, 0x57, 0xb8, 0x50,
0x7c, 0x6e, 0xe2, 0xe2, 0xd5, 0xbc, 0xfa, 0x59, 0x99, 0x92, 0x42, 0x47, 0xd7, 0x18, 0x7b, 0x14,
0x52, 0xec, 0xdf, 0x9c, 0x30, 0xfb, 0xc7, 0x06, 0x9c, 0x3c, 0x0f, 0x13, 0x51, 0xd0, 0x09, 0x5d,
0x8a, 0xb4, 0x1d, 0x44, 0x73, 0xd6, 0x85, 0x22, 0x9b, 0x7a, 0x6c, 0xa6, 0x6e, 0x6b, 0x30, 0x9a,
0x34, 0xe4, 0x73, 0x16, 0x4c, 0x56, 0x69, 0x14, 0x7b, 0x3e, 0x97, 0x9f, 0x28, 0xbf, 0x33, 0xb4,
0xf2, 0x09, 0x70, 0x55, 0x33, 0xaf, 0x9c, 0x91, 0x1d, 0x99, 0x34, 0x80, 0x11, 0xa6, 0xe4, 0xb3,
0x15, 0x57, 0xa5, 0x91, 0x1b, 0x7a, 0x6d, 0xf6, 0x9b, 0xcf, 0x19, 0x63, 0xc5, 0xad, 0x6a, 0x14,
0x9a, 0x74, 0xc4, 0x87, 0x51, 0xb6, 0xa2, 0xa2, 0xb9, 0x11, 0xae, 0xff, 0xda, 0x70, 0xfa, 0xcb,
0x41, 0x65, 0x8b, 0x55, 0x8f, 0x3e, 0xfb, 0x15, 0xa1, 0x10, 0x43, 0x3e, 0x6b, 0xc1, 0x9c, 0x5c,
0xf1, 0x48, 0xc5, 0x80, 0xde, 0x69, 0x78, 0x31, 0x6d, 0x7a, 0x51, 0x3c, 0x37, 0xca, 0x75, 0x58,
0x3a, 0xde, 0xdc, 0xba, 0x12, 0x06, 0x9d, 0xf6, 0x75, 0xcf, 0xaf, 0x56, 0x2e, 0x48, 0x49, 0x73,
0x2b, 0x03, 0x18, 0xe3, 0x40, 0x91, 0xe4, 0x4b, 0x16, 0xcc, 0xfb, 0x4e, 0x8b, 0x46, 0x6d, 0x87,
0x7d, 0x5a, 0x81, 0xae, 0x34, 0x1d, 0x77, 0x8f, 0x6b, 0x34, 0xf6, 0x68, 0x1a, 0xd9, 0x52, 0xa3,
0xf9, 0x1b, 0x03, 0x59, 0xe3, 0x43, 0xc4, 0x92, 0x5f, 0xb2, 0x60, 0x36, 0x08, 0xdb, 0x0d, 0xc7,
0xa7, 0xd5, 0x04, 0x1b, 0xcd, 0x8d, 0xf3, 0xa5, 0xf7, 0xc1, 0xe1, 0x3e, 0xd1, 0x66, 0x96, 0xed,
0x46, 0xe0, 0x7b, 0x71, 0x10, 0x6e, 0xd3, 0x38, 0xf6, 0xfc, 0x7a, 0x54, 0x39, 0x7b, 0xff, 0x70,
0x61, 0xb6, 0x87, 0x0a, 0x7b, 0xf5, 0x21, 0x1f, 0x86, 0x89, 0xa8, 0xeb, 0xbb, 0x77, 0x3c, 0xbf,
0x1a, 0xdc, 0x8b, 0xe6, 0x4a, 0x79, 0x2c, 0xdf, 0x6d, 0xc5, 0x50, 0x2e, 0x40, 0x2d, 0x00, 0x4d,
0x69, 0xfd, 0x3f, 0x9c, 0x9e, 0x4a, 0xe5, 0xbc, 0x3f, 0x9c, 0x9e, 0x4c, 0x0f, 0x11, 0x4b, 0x3e,
0x65, 0xc1, 0x54, 0xe4, 0xd5, 0x7d, 0x27, 0xee, 0x84, 0xf4, 0x3a, 0xed, 0x46, 0x73, 0xc0, 0x15,
0xb9, 0x36, 0xe4, 0xa8, 0x18, 0x2c, 0x2b, 0x67, 0xa5, 0x8e, 0x53, 0x26, 0x34, 0xc2, 0xb4, 0xdc,
0x7e, 0x0b, 0x4d, 0x4f, 0xeb, 0x89, 0x7c, 0x17, 0x9a, 0x9e, 0xd4, 0x03, 0x45, 0x92, 0xf7, 0xc2,
0x8c, 0x00, 0xa9, 0x91, 0x8d, 0xe6, 0x26, 0xb9, 0xa1, 0x3d, 0x73, 0xff, 0x70, 0x61, 0x66, 0x3b,
0x83, 0xc3, 0x1e, 0x6a, 0xfb, 0x0f, 0x0b, 0x30, 0x93, 0xdd, 0xc5, 0xc8, 0xaf, 0x58, 0x70, 0xea,
0xee, 0xbd, 0x78, 0x27, 0xd8, 0xa3, 0x7e, 0x54, 0xe9, 0x32, 0x5b, 0xc3, 0xed, 0xf7, 0xc4, 0x45,
0x37, 0xdf, 0xfd, 0x72, 0xf1, 0x5a, 0x5a, 0xca, 0x25, 0x3f, 0x0e, 0xbb, 0x95, 0xa7, 0xe5, 0x88,
0x9c, 0xba, 0x76, 0x67, 0xc7, 0xc4, 0x62, 0x56, 0xa9, 0xf9, 0x4f, 0x5b, 0x70, 0xa6, 0x1f, 0x0b,
0x32, 0x03, 0xc5, 0x3d, 0xda, 0x15, 0x2e, 0x12, 0xb2, 0x3f, 0xc9, 0x8f, 0xc3, 0xe8, 0xbe, 0xd3,
0xec, 0x50, 0xe9, 0x6a, 0x5c, 0x19, 0xae, 0x23, 0x4a, 0x33, 0x14, 0x5c, 0xdf, 0x55, 0x78, 0xd1,
0xb2, 0xff, 0xb8, 0x08, 0x13, 0xc6, 0x66, 0xf3, 0x04, 0xdc, 0xa7, 0x20, 0xe5, 0x3e, 0x6d, 0xe4,
0xb6, 0x4f, 0x0e, 0xf4, 0x9f, 0xee, 0x65, 0xfc, 0xa7, 0xcd, 0xfc, 0x44, 0x3e, 0xd4, 0x81, 0x22,
0x31, 0x94, 0x83, 0x36, 0x73, 0x8f, 0xd9, 0x3e, 0x3c, 0x92, 0xc7, 0x27, 0xdc, 0x4c, 0xd8, 0x55,
0xa6, 0xee, 0x1f, 0x2e, 0x94, 0xd5, 0x4f, 0xd4, 0x82, 0xec, 0x6f, 0x59, 0x70, 0xc6, 0xd0, 0x71,
0x25, 0xf0, 0xab, 0x1e, 0xff, 0xb4, 0x17, 0x60, 0x24, 0xee, 0xb6, 0x13, 0x1f, 0x5c, 0x8d, 0xd4,
0x4e, 0xb7, 0x4d, 0x91, 0x63, 0x98, 0xd7, 0xdd, 0xa2, 0x51, 0xe4, 0xd4, 0x69, 0xd6, 0xeb, 0xde,
0x10, 0x60, 0x4c, 0xf0, 0x24, 0x04, 0xd2, 0x74, 0xa2, 0x78, 0x27, 0x74, 0xfc, 0x88, 0xb3, 0xdf,
0xf1, 0x5a, 0x54, 0x0e, 0xf0, 0xff, 0x39, 0xde, 0x8c, 0x61, 0x2d, 0x2a, 0xe7, 0xee, 0x1f, 0x2e,
0x90, 0xf5, 0x1e, 0x4e, 0xd8, 0x87, 0xbb, 0xfd, 0x25, 0x0b, 0xce, 0xf5, 0x77, 0x8c, 0xc8, 0x9b,
0x61, 0x2c, 0xa2, 0xe1, 0x3e, 0x0d, 0x65, 0xef, 0xf4, 0x27, 0xe1, 0x50, 0x94, 0x58, 0xb2, 0x04,
0x65, 0x65, 0xb4, 0x65, 0x1f, 0x67, 0x25, 0x69, 0x59, 0x5b, 0x7a, 0x4d, 0xc3, 0x06, 0x8d, 0xfd,
0x90, 0x6e, 0x94, 0x1a, 0x34, 0x1e, 0xb1, 0x70, 0x8c, 0xfd, 0x57, 0x16, 0x9c, 0x32, 0xb4, 0x7a,
0x02, 0x7e, 0xb2, 0x9f, 0xf6, 0x93, 0xd7, 0x72, 0x9b, 0xcf, 0x03, 0x1c, 0xe5, 0x3f, 0x18, 0x85,
0x59, 0x73, 0xd6, 0x73, 0x7b, 0xcc, 0x43, 0x34, 0xda, 0x0e, 0x6e, 0xe1, 0xba, 0x1c, 0x73, 0x1d,
0xa2, 0x09, 0x30, 0x26, 0x78, 0x36, 0x88, 0x6d, 0x27, 0x6e, 0xc8, 0x01, 0x57, 0x83, 0xb8, 0xe5,
0xc4, 0x0d, 0xe4, 0x18, 0xf2, 0x32, 0x4c, 0xc7, 0x4e, 0x58, 0xa7, 0x31, 0xd2, 0x7d, 0x2f, 0x4a,
0xd6, 0x4b, 0xb9, 0x72, 0x4e, 0xd2, 0x4e, 0xef, 0xa4, 0xb0, 0x98, 0xa1, 0x26, 0xaf, 0xc1, 0x48,
0x83, 0x36, 0x5b, 0xd2, 0x33, 0xda, 0xce, 0x6f, 0x85, 0xf3, 0xbe, 0x5e, 0xa5, 0xcd, 0x56, 0xa5,
0xc4, 0x54, 0x66, 0x7f, 0x21, 0x17, 0x45, 0x7e, 0xca, 0x82, 0xf2, 0x5e, 0x27, 0x8a, 0x83, 0x96,
0xf7, 0x21, 0x3a, 0x57, 0xe2, 0x82, 0x7f, 0x2c, 0x67, 0xc1, 0xd7, 0x13, 0xfe, 0x62, 0xbd, 0xab,
0x9f, 0xa8, 0x25, 0x73, 0x3d, 0xaa, 0x5e, 0x48, 0xdd, 0x38, 0x08, 0xbb, 0x73, 0xf0, 0x58, 0xf4,
0x58, 0x4d, 0xf8, 0x0b, 0x3d, 0xd4, 0x4f, 0xd4, 0x92, 0x49, 0x17, 0xc6, 0xda, 0xcd, 0x4e, 0xdd,
0xf3, 0xe7, 0x26, 0xb8, 0x0e, 0xb7, 0x72, 0xd6, 0x61, 0x8b, 0x33, 0xaf, 0x00, 0x5b, 0xd5, 0xe2,
0x6f, 0x94, 0x02, 0xc9, 0xb3, 0x30, 0xea, 0x36, 0x9c, 0x30, 0x9e, 0x9b, 0xe4, 0x93, 0x46, 0xcd,
0xe2, 0x15, 0x06, 0x44, 0x81, 0xb3, 0x7f, 0xa1, 0x00, 0xf3, 0x83, 0x3b, 0x26, 0xa6, 0xb3, 0xdb,
0x09, 0x23, 0x61, 0x20, 0x4b, 0xe6, 0x74, 0xe6, 0x60, 0x4c, 0xf0, 0xe4, 0x13, 0x16, 0x8c, 0xdf,
0x8d, 0x02, 0xdf, 0xa7, 0xb1, 0xdc, 0xc5, 0x6e, 0xe7, 0xdc, 0xd7, 0x6b, 0x82, 0xbb, 0xd6, 0x41,
0x02, 0x30, 0x91, 0xcb, 0xd4, 0xa5, 0x07, 0x6e, 0xb3, 0x53, 0x4d, 0x4c, 0x93, 0x22, 0xbd, 0x24,
0xc0, 0x98, 0xe0, 0x19, 0xa9, 0xe7, 0x0b, 0xd2, 0x91, 0x34, 0xe9, 0x9a, 0x2f, 0x49, 0x25, 0xde,
0xfe, 0xf5, 0x51, 0x38, 0xdb, 0x77, 0xf6, 0x93, 0x45, 0x00, 0xee, 0x34, 0x5c, 0xf6, 0x58, 0x8c,
0x28, 0x02, 0xe3, 0x69, 0xb6, 0xc7, 0xdf, 0x56, 0x50, 0x34, 0x28, 0xc8, 0xc7, 0x00, 0xda, 0x4e,
0xe8, 0xb4, 0x68, 0x4c, 0xc3, 0xc4, 0x50, 0x5d, 0x1f, 0x6e, 0x94, 0x98, 0x1e, 0x5b, 0x09, 0x4f,
0xed, 0x64, 0x28, 0x50, 0x84, 0x86, 0x48, 0x16, 0x06, 0x87, 0xb4, 0x49, 0x9d, 0x88, 0x7b, 0x8e,
0xd9, 0x30, 0x18, 0x35, 0x0a, 0x4d, 0x3a, 0xb6, 0x91, 0xf0, 0x5e, 0x44, 0x72, 0xac, 0xd4, 0x46,
0xc2, 0xfb, 0x19, 0xa1, 0xc4, 0x92, 0xcf, 0x5b, 0x30, 0x5d, 0xf3, 0x9a, 0x54, 0x4b, 0x97, 0x41,
0xeb, 0xe6, 0xf0, 0x9d, 0xbc, 0x6c, 0xf2, 0xd5, 0x26, 0x30, 0x05, 0x8e, 0x30, 0x23, 0x9e, 0x7d,
0xe6, 0x7d, 0x1a, 0x72, 0xdb, 0x39, 0x96, 0xfe, 0xcc, 0xb7, 0x05, 0x18, 0x13, 0x3c, 0x59, 0x86,
0x53, 0x6d, 0x27, 0x8a, 0x56, 0x42, 0x5a, 0xa5, 0x7e, 0xec, 0x39, 0x4d, 0x11, 0x52, 0x96, 0xb4,
0x17, 0xbb, 0x95, 0x46, 0x63, 0x96, 0x9e, 0xbc, 0x0f, 0x9e, 0xf6, 0xea, 0x7e, 0x10, 0xd2, 0x0d,
0x2f, 0x8a, 0x3c, 0xbf, 0xae, 0xa7, 0x01, 0x37, 0x85, 0xa5, 0xca, 0x82, 0x64, 0xf5, 0xf4, 0x5a,
0x7f, 0x32, 0x1c, 0xd4, 0x9e, 0xbc, 0x0d, 0x4a, 0xd1, 0x9e, 0xd7, 0x5e, 0x09, 0xab, 0xd1, 0x5c,
0x99, 0xf3, 0x52, 0x9b, 0xe1, 0xb6, 0x84, 0xa3, 0xa2, 0xb0, 0xbf, 0x52, 0x80, 0xb9, 0x41, 0xeb,
0x87, 0x44, 0x6c, 0x95, 0xc4, 0xb7, 0x9d, 0x30, 0x92, 0xb1, 0xc0, 0x90, 0x41, 0xa9, 0xe4, 0x7b,
0xdb, 0x09, 0xcd, 0xf5, 0xc6, 0x05, 0x60, 0x22, 0x89, 0xdc, 0x85, 0x91, 0xb8, 0xe9, 0xe4, 0x94,
0xc5, 0x32, 0x24, 0x6a, 0x8f, 0x6d, 0x7d, 0x39, 0x42, 0x2e, 0x83, 0x3c, 0x03, 0x23, 0x4d, 0x6f,
0x97, 0x79, 0xb6, 0x6c, 0x41, 0xf2, 0x2d, 0x6a, 0xdd, 0xdb, 0x8d, 0x90, 0x43, 0xed, 0x7f, 0x1e,
0xeb, 0x63, 0xf2, 0xd4, 0x26, 0x42, 0x2e, 0x02, 0x30, 0x0f, 0x66, 0x2b, 0xa4, 0x35, 0xef, 0x40,
0x6e, 0xe2, 0x6a, 0x59, 0xdd, 0x50, 0x18, 0x34, 0xa8, 0x92, 0x36, 0xdb, 0x9d, 0x1a, 0x6b, 0x53,
0xe8, 0x6d, 0x23, 0x30, 0x68, 0x50, 0x91, 0x17, 0x60, 0xcc, 0x6b, 0x39, 0x75, 0x9a, 0xa8, 0xf9,
0x0c, 0x5b, 0x4f, 0x6b, 0x1c, 0xf2, 0xe0, 0x70, 0x61, 0x5a, 0x29, 0xc4, 0x41, 0x28, 0x69, 0xc9,
0x2f, 0x5b, 0x30, 0xe9, 0x06, 0xad, 0x56, 0xe0, 0xaf, 0x3b, 0xbb, 0xb4, 0x99, 0x24, 0xa6, 0xee,
0x3e, 0xae, 0x2d, 0x76, 0x71, 0xc5, 0x10, 0x26, 0x82, 0x3a, 0x95, 0x6e, 0x33, 0x51, 0x98, 0xd2,
0xca, 0x5c, 0x76, 0xa3, 0x47, 0x2c, 0xbb, 0xdf, 0xb6, 0x60, 0x56, 0xb4, 0x5d, 0xf6, 0xfd, 0x20,
0x96, 0xf9, 0x42, 0x91, 0x59, 0x0a, 0x1e, 0x73, 0xb7, 0x0c, 0x89, 0xa2, 0x6f, 0x6f, 0x90, 0x6a,
0xce, 0xf6, 0xe0, 0xb1, 0x57, 0x49, 0x72, 0x05, 0x66, 0x6b, 0x41, 0xe8, 0x52, 0x73, 0x20, 0xa4,
0xcd, 0x50, 0x8c, 0x2e, 0x67, 0x09, 0xb0, 0xb7, 0x0d, 0xb9, 0x0d, 0xe7, 0x0c, 0xa0, 0x39, 0x0e,
0xc2, 0x6c, 0x9c, 0x97, 0xdc, 0xce, 0x5d, 0xee, 0x4b, 0x85, 0x03, 0x5a, 0xcf, 0xbf, 0x07, 0x66,
0x7b, 0xbe, 0x5f, 0x9f, 0x88, 0xfa, 0x8c, 0x19, 0x51, 0x97, 0x8d, 0x40, 0x78, 0x7e, 0x15, 0xce,
0xf5, 0x1f, 0xa9, 0x93, 0x70, 0xb1, 0x7f, 0xce, 0x82, 0xa7, 0x07, 0x78, 0x2e, 0x2a, 0x94, 0xb0,
0x06, 0x85, 0x12, 0xc4, 0x81, 0x22, 0xf5, 0xf7, 0xa5, 0xe1, 0xb8, 0x3c, 0xdc, 0x8c, 0xb8, 0xe4,
0xef, 0x8b, 0x0f, 0x3d, 0x7e, 0xff, 0x70, 0xa1, 0x78, 0xc9, 0xdf, 0x47, 0xc6, 0xdb, 0xfe, 0x99,
0xb1, 0x54, 0xb4, 0xb2, 0x9d, 0x04, 0xc8, 0x5c, 0x51, 0x19, 0xab, 0x6c, 0xe6, 0x3c, 0x17, 0x8d,
0x68, 0x4c, 0x24, 0xce, 0xa5, 0x38, 0xf2, 0x69, 0x8b, 0xe7, 0xaa, 0x93, 0x28, 0x4e, 0x3a, 0x53,
0x8f, 0x27, 0x75, 0x6e, 0x66, 0xc0, 0x13, 0x20, 0x9a, 0xd2, 0xd9, 0x4a, 0x6e, 0x8b, 0x44, 0x4f,
0xd6, 0xa5, 0x4a, 0xb2, 0xd9, 0x09, 0x9e, 0x1c, 0x00, 0x44, 0x5d, 0xdf, 0xdd, 0x0a, 0x9a, 0x9e,
0xdb, 0x95, 0xa1, 0x7d, 0x0e, 0xf9, 0x4e, 0xc1, 0x4f, 0xf8, 0x55, 0xfa, 0x37, 0x1a, 0xb2, 0xc8,
0x57, 0x2d, 0x98, 0x15, 0x1b, 0xe7, 0xaa, 0x57, 0xab, 0xd1, 0x90, 0xfa, 0x2e, 0x4d, 0x5c, 0x8f,
0x3b, 0xc3, 0x69, 0x90, 0xa4, 0xea, 0xd6, 0xb2, 0xec, 0xf5, 0x12, 0xef, 0x41, 0x61, 0xaf, 0x32,
0xa4, 0x0a, 0x23, 0x9e, 0x5f, 0x0b, 0xa4, 0x61, 0xab, 0x0c, 0xa7, 0xd4, 0x9a, 0x5f, 0x0b, 0xf4,
0x5a, 0x61, 0xbf, 0x90, 0x73, 0x27, 0xeb, 0x70, 0x26, 0x94, 0xd1, 0xdf, 0x55, 0x2f, 0x62, 0x2e,
0xfc, 0xba, 0xd7, 0xf2, 0x62, 0x6e, 0x94, 0x8a, 0x95, 0xb9, 0xfb, 0x87, 0x0b, 0x67, 0xb0, 0x0f,
0x1e, 0xfb, 0xb6, 0xb2, 0x5f, 0x2f, 0xa7, 0x43, 0x5c, 0x91, 0xc0, 0xf9, 0x08, 0x94, 0x43, 0x95,
0x74, 0x17, 0x0e, 0xc4, 0x7a, 0x3e, 0x63, 0x2c, 0x33, 0x47, 0x2a, 0xf7, 0xa0, 0xd3, 0xeb, 0x5a,
0x22, 0x73, 0x24, 0xd8, 0x97, 0x97, 0xcb, 0x22, 0x87, 0xf9, 0x25, 0xa5, 0xea, 0x24, 0x59, 0xd7,
0x77, 0x91, 0xcb, 0x20, 0x21, 0x8c, 0x35, 0xa8, 0xd3, 0x8c, 0x1b, 0x32, 0x87, 0x73, 0x6d, 0x58,
0x37, 0x96, 0xf1, 0xca, 0xe6, 0xc7, 0x04, 0x14, 0xa5, 0x24, 0x72, 0x00, 0xe3, 0x0d, 0xf1, 0x11,
0xe4, 0xde, 0xbe, 0x31, 0xec, 0xe0, 0xa6, 0xbe, 0xac, 0x5e, 0xbf, 0x12, 0x80, 0x89, 0x38, 0xf2,
0xd3, 0x16, 0x80, 0x9b, 0x24, 0xc6, 0x92, 0xe5, 0x83, 0xb9, 0xd9, 0x1d, 0x95, 0x73, 0xd3, 0xae,
0x91, 0x02, 0x45, 0x68, 0x48, 0x26, 0xaf, 0xc2, 0x64, 0x48, 0xdd, 0xc0, 0x77, 0xbd, 0x26, 0xad,
0x2e, 0xc7, 0xdc, 0x73, 0x3f, 0x59, 0x02, 0x6d, 0x86, 0xf9, 0x27, 0x68, 0xf0, 0xc0, 0x14, 0x47,
0xf2, 0xba, 0x05, 0xd3, 0x2a, 0x39, 0xc8, 0x3e, 0x08, 0x95, 0x49, 0x92, 0xf5, 0x9c, 0x52, 0x91,
0x9c, 0x67, 0x85, 0xb0, 0x08, 0x25, 0x0d, 0xc3, 0x8c, 0x5c, 0xf2, 0x0a, 0x40, 0xb0, 0xcb, 0x13,
0x71, 0xac, 0xab, 0xa5, 0x13, 0x77, 0x75, 0x5a, 0xe4, 0x94, 0x13, 0x0e, 0x68, 0x70, 0x23, 0xd7,
0x01, 0xc4, 0xb2, 0xd9, 0xe9, 0xb6, 0x29, 0x0f, 0x1b, 0xca, 0x95, 0xb7, 0x26, 0x83, 0xbf, 0xad,
0x30, 0x0f, 0x0e, 0x17, 0x7a, 0x03, 0x5c, 0x9e, 0x01, 0x35, 0x9a, 0x93, 0x0f, 0xc3, 0x78, 0xd4,
0x69, 0xb5, 0x1c, 0x95, 0x4f, 0xd9, 0xca, 0x6f, 0x47, 0x14, 0x7c, 0xf5, 0xdc, 0x94, 0x00, 0x4c,
0x24, 0xda, 0x3e, 0x90, 0x5e, 0x7a, 0xf2, 0x02, 0x4c, 0xd2, 0x83, 0x98, 0x86, 0xbe, 0xd3, 0xbc,
0x85, 0xeb, 0x49, 0x04, 0xce, 0x3f, 0xfe, 0x25, 0x03, 0x8e, 0x29, 0x2a, 0x62, 0x2b, 0xcf, 0xbb,
0xc0, 0xe9, 0x41, 0x7b, 0xde, 0x89, 0x9f, 0x6d, 0xff, 0x47, 0x21, 0xe5, 0x11, 0xec, 0x84, 0x94,
0x92, 0x00, 0x46, 0xfd, 0xa0, 0xaa, 0x8c, 0xde, 0xb5, 0x7c, 0x8c, 0xde, 0x8d, 0xa0, 0x6a, 0x9c,
0x06, 0xb3, 0x5f, 0x11, 0x0a, 0x39, 0xfc, 0xb8, 0x2c, 0x39, 0x57, 0xe4, 0x08, 0xe9, 0x04, 0xe5,
0x29, 0x59, 0x1d, 0x97, 0x6d, 0x9a, 0x82, 0x30, 0x2d, 0x97, 0xec, 0xc1, 0x68, 0x23, 0x88, 0x62,
0x11, 0xab, 0x0c, 0xed, 0x85, 0x5d, 0x0d, 0xa2, 0x98, 0x6f, 0x61, 0xaa, 0xdb, 0x0c, 0x12, 0xa1,
0x90, 0x61, 0x7f, 0xdf, 0x4a, 0xe5, 0x5b, 0xee, 0x38, 0xb1, 0xdb, 0xb8, 0xb4, 0x4f, 0x7d, 0x36,
0x9f, 0xcd, 0x64, 0xfd, 0xff, 0x37, 0x93, 0xf5, 0x0f, 0x0e, 0x17, 0xde, 0x32, 0xa8, 0x3c, 0xe7,
0x1e, 0xe3, 0xb0, 0xc8, 0x59, 0x18, 0x79, 0xfd, 0x8f, 0x5b, 0x30, 0x61, 0xa8, 0x27, 0x37, 0x94,
0x1c, 0xf3, 0xc6, 0xca, 0xb9, 0x32, 0x80, 0x68, 0x8a, 0xb4, 0xbf, 0x68, 0xc1, 0x78, 0xc5, 0x71,
0xf7, 0x82, 0x5a, 0x8d, 0x05, 0xf8, 0xd5, 0x8e, 0x3c, 0x16, 0x11, 0xfd, 0x53, 0x01, 0xfe, 0xaa,
0x84, 0xa3, 0xa2, 0x60, 0x73, 0xb8, 0xe6, 0xb8, 0x71, 0x10, 0x72, 0xb5, 0x8b, 0x62, 0x0e, 0x5f,
0xe6, 0x10, 0x94, 0x18, 0xf2, 0x0e, 0x98, 0x68, 0x39, 0x07, 0x49, 0xe3, 0x6c, 0xb2, 0x67, 0x43,
0xa3, 0xd0, 0xa4, 0xb3, 0x7f, 0xbf, 0x0c, 0xe3, 0xf2, 0x04, 0xf3, 0xd8, 0x27, 0x08, 0x89, 0x17,
0x5f, 0x18, 0xe8, 0xc5, 0x47, 0x30, 0xe6, 0xf2, 0xe2, 0x27, 0xb9, 0x95, 0x0e, 0x99, 0xf6, 0x92,
0x0a, 0x8a, 0x7a, 0x2a, 0xad, 0x96, 0xf8, 0x8d, 0x52, 0x14, 0xf9, 0x82, 0x05, 0xa7, 0xdc, 0xc0,
0xf7, 0xa9, 0xab, 0xed, 0xfc, 0x48, 0x1e, 0x27, 0x6c, 0x2b, 0x69, 0xa6, 0x3a, 0x45, 0x94, 0x41,
0x60, 0x56, 0x3c, 0x79, 0x09, 0xa6, 0xc4, 0x98, 0xdd, 0x4e, 0xc5, 0xc7, 0xfa, 0xd4, 0xda, 0x44,
0x62, 0x9a, 0x96, 0x2c, 0x8a, 0x3c, 0x83, 0x3c, 0x1f, 0x1e, 0xd3, 0xf9, 0x46, 0xe3, 0x64, 0xd8,
0xa0, 0x20, 0x21, 0x90, 0x90, 0xd6, 0x42, 0x1a, 0x35, 0x90, 0xbe, 0xd6, 0xa1, 0x51, 0xcc, 0xf7,
0x98, 0xf1, 0x47, 0x3b, 0x8f, 0xc2, 0x1e, 0x4e, 0xd8, 0x87, 0x3b, 0xd9, 0x93, 0x8e, 0x6e, 0x29,
0x8f, 0xe5, 0x24, 0x3f, 0xf3, 0x40, 0x7f, 0x77, 0x01, 0x46, 0xa3, 0x86, 0x13, 0x56, 0xf9, 0xde,
0x56, 0xac, 0x94, 0x99, 0x2d, 0xd9, 0x66, 0x00, 0x14, 0x70, 0xb2, 0x0a, 0x33, 0x99, 0x33, 0xf7,
0x88, 0xef, 0x5e, 0xa5, 0xca, 0x9c, 0x64, 0x37, 0x93, 0x39, 0xad, 0x8f, 0xb0, 0xa7, 0x85, 0x19,
0x04, 0x4d, 0x1c, 0x11, 0x04, 0x75, 0x61, 0xac, 0x29, 0x12, 0x01, 0x93, 0xdc, 0x54, 0xde, 0xcc,
0x65, 0x00, 0x16, 0xcd, 0x04, 0x8c, 0x9a, 0xed, 0x32, 0xa1, 0x20, 0x05, 0x92, 0xcf, 0x32, 0x83,
0x66, 0xe4, 0x0e, 0xa6, 0xb8, 0x02, 0xb7, 0xf3, 0x51, 0xa0, 0x27, 0x55, 0xa2, 0xad, 0x9b, 0x91,
0x88, 0x30, 0xe5, 0xcf, 0xff, 0x08, 0x4c, 0x3c, 0x6a, 0xde, 0xe1, 0x65, 0x98, 0x19, 0x2a, 0xe3,
0xf0, 0xef, 0x16, 0x24, 0xdf, 0x75, 0xc5, 0x71, 0x1b, 0x94, 0x4d, 0x19, 0xf2, 0x32, 0x4c, 0xab,
0x30, 0x62, 0x25, 0xe8, 0xf8, 0x31, 0xe7, 0x55, 0xd4, 0xb9, 0x64, 0x4c, 0x61, 0x31, 0x43, 0x4d,
0x96, 0xa0, 0xcc, 0xc6, 0x49, 0x34, 0x15, 0x66, 0x57, 0x85, 0x2a, 0xcb, 0x5b, 0x6b, 0xb2, 0x95,
0xa6, 0x21, 0x01, 0xcc, 0x36, 0x9d, 0x28, 0xe6, 0x1a, 0xb0, 0xa8, 0xe2, 0x11, 0x4f, 0x83, 0x79,
0xc9, 0xd1, 0x7a, 0x96, 0x11, 0xf6, 0xf2, 0xb6, 0xbf, 0x35, 0x02, 0x53, 0x29, 0xcb, 0xc8, 0x76,
0x95, 0x4e, 0xc4, 0x5c, 0x1f, 0x95, 0x62, 0x51, 0xbb, 0xca, 0x2d, 0x09, 0x47, 0x45, 0xc1, 0xa8,
0xdb, 0x4e, 0x14, 0xdd, 0x0b, 0xc2, 0xaa, 0x34, 0xe5, 0x8a, 0x7a, 0x4b, 0xc2, 0x51, 0x51, 0xb0,
0xfd, 0x65, 0x97, 0x3a, 0x21, 0x0d, 0x79, 0x01, 0x45, 0x76, 0x7f, 0xa9, 0x68, 0x14, 0x9a, 0x74,
0xdc, 0x28, 0xc7, 0xcd, 0x68, 0xa5, 0xe9, 0x51, 0x3f, 0x16, 0x6a, 0xe6, 0x63, 0x94, 0x77, 0xd6,
0xb7, 0x4d, 0xa6, 0xda, 0x28, 0x67, 0x10, 0x98, 0x15, 0x4f, 0x3e, 0x69, 0xc1, 0x94, 0x73, 0x2f,
0xd2, 0x15, 0xba, 0xdc, 0x2a, 0x0f, 0xbd, 0x49, 0xa5, 0x8a, 0x7e, 0x2b, 0xb3, 0xcc, 0xbc, 0xa7,
0x40, 0x98, 0x16, 0x4a, 0xbe, 0x6c, 0x01, 0xa1, 0x07, 0xd4, 0xdd, 0x0a, 0x83, 0x7d, 0xaf, 0x9a,
0x7c, 0x43, 0x19, 0xfe, 0x0c, 0xe9, 0x6d, 0x5f, 0xea, 0xe1, 0x2b, 0xac, 0x7a, 0x2f, 0x1c, 0xfb,
0xe8, 0x60, 0xff, 0x45, 0x11, 0x26, 0x0c, 0x63, 0xdc, 0x77, 0x67, 0xb5, 0x7e, 0xc0, 0x76, 0xd6,
0xc2, 0x09, 0x76, 0xd6, 0x8f, 0x41, 0xd9, 0x4d, 0x0c, 0x45, 0x3e, 0x15, 0xc5, 0x59, 0xf3, 0xa3,
0x6d, 0x85, 0x02, 0xa1, 0x96, 0x49, 0xae, 0xc0, 0xac, 0xc1, 0x46, 0x1a, 0x99, 0x11, 0x6e, 0x64,
0x54, 0xa2, 0x69, 0x39, 0x4b, 0x80, 0xbd, 0x6d, 0xc8, 0xf3, 0xcc, 0xab, 0xf5, 0x64, 0xbf, 0x44,
0x14, 0x2f, 0xab, 0x75, 0x97, 0xb7, 0xd6, 0x12, 0x30, 0x9a, 0x34, 0xf6, 0xb7, 0x2c, 0xf5, 0x71,
0x9f, 0x40, 0xa1, 0xc6, 0xdd, 0x74, 0xa1, 0xc6, 0xa5, 0x5c, 0x86, 0x79, 0x40, 0x91, 0xc6, 0x0d,
0x18, 0x5f, 0x09, 0x5a, 0x2d, 0xc7, 0xaf, 0x92, 0x37, 0xc1, 0xb8, 0x2b, 0xfe, 0x94, 0x61, 0xe2,
0x04, 0xdb, 0xbf, 0x25, 0x16, 0x13, 0x1c, 0x79, 0x06, 0x46, 0x9c, 0xb0, 0x9e, 0x84, 0x86, 0xfc,
0xec, 0x68, 0x39, 0xac, 0x47, 0xc8, 0xa1, 0xf6, 0x97, 0x0a, 0x00, 0x2b, 0x41, 0xab, 0xed, 0x84,
0xb4, 0xba, 0x13, 0xfc, 0x4f, 0x8e, 0x58, 0x44, 0x0c, 0x9f, 0xb1, 0x80, 0xb0, 0x51, 0x09, 0x7c,
0xea, 0xc7, 0xea, 0xf0, 0x95, 0xed, 0x97, 0x6e, 0x02, 0x95, 0x9b, 0x8f, 0x5e, 0x03, 0x09, 0x02,
0x35, 0xcd, 0x31, 0xa2, 0x88, 0x67, 0x93, 0x1d, 0xbf, 0x98, 0xae, 0x69, 0xe0, 0x07, 0xa5, 0xd2,
0x01, 0xb0, 0xbf, 0x5e, 0x80, 0x73, 0xc2, 0x6c, 0x6d, 0x38, 0xbe, 0x53, 0xa7, 0x2d, 0xa6, 0xd5,
0x71, 0x4f, 0x1b, 0x5c, 0xe6, 0xbe, 0x7a, 0x49, 0x09, 0xc3, 0xb0, 0x93, 0x53, 0x4c, 0x2a, 0x31,
0x8d, 0xd6, 0x7c, 0x2f, 0x46, 0xce, 0x9c, 0x44, 0x50, 0x4a, 0xee, 0x88, 0x48, 0x63, 0x93, 0x93,
0x20, 0xb5, 0xee, 0xae, 0x48, 0xf6, 0xa8, 0x04, 0xb1, 0xcd, 0xbd, 0x19, 0xb8, 0x7b, 0x48, 0xdb,
0x01, 0x37, 0x2c, 0xc6, 0x09, 0xf2, 0xba, 0x84, 0xa3, 0xa2, 0xb0, 0xbf, 0x6e, 0x41, 0xd6, 0xe4,
0xf2, 0x68, 0x50, 0xd4, 0x0c, 0x66, 0xa3, 0xc1, 0x74, 0x89, 0xdf, 0x09, 0x2a, 0xe6, 0x3e, 0x00,
0x13, 0x4e, 0x1c, 0xd3, 0x56, 0x5b, 0x84, 0x26, 0xc5, 0x47, 0x4b, 0x7f, 0x6d, 0x04, 0x55, 0xaf,
0xe6, 0xf1, 0x90, 0xc4, 0x64, 0x67, 0xdf, 0x84, 0x52, 0x72, 0xe2, 0x73, 0x8c, 0x4f, 0xff, 0x6c,
0xca, 0x9d, 0x1c, 0x30, 0xb9, 0x1e, 0x14, 0xa0, 0xcf, 0x9e, 0xc9, 0xba, 0xac, 0xad, 0x4b, 0xaa,
0xcb, 0x27, 0xb3, 0x30, 0xe4, 0x40, 0x9c, 0x76, 0x89, 0x3c, 0xcb, 0xfb, 0xf2, 0xde, 0xf3, 0xf5,
0x01, 0xd8, 0x84, 0xd4, 0x4f, 0x1d, 0x82, 0x91, 0x8b, 0x00, 0x7a, 0x53, 0x90, 0x85, 0x1e, 0x2a,
0x53, 0xab, 0xf7, 0x0e, 0x34, 0xa8, 0x98, 0x0b, 0xe8, 0xf9, 0x51, 0xec, 0x34, 0x9b, 0x57, 0x3d,
0x3f, 0x96, 0xb1, 0xac, 0x32, 0x18, 0x6b, 0x1a, 0x85, 0x26, 0xdd, 0xfc, 0x3b, 0x8d, 0xef, 0x72,
0x12, 0xb7, 0xfe, 0x33, 0x05, 0x98, 0xbe, 0xe2, 0x77, 0xb6, 0xae, 0x6c, 0x75, 0x76, 0x9b, 0x9e,
0x7b, 0x9d, 0x76, 0xd9, 0x47, 0xdb, 0xa3, 0xdd, 0xb5, 0x55, 0x39, 0xec, 0xea, 0xa3, 0x5d, 0x67,
0x40, 0x14, 0x38, 0xa6, 0x66, 0xcd, 0xf3, 0xeb, 0x34, 0x6c, 0x87, 0x9e, 0xf4, 0xdd, 0x0d, 0x35,
0x2f, 0x6b, 0x14, 0x9a, 0x74, 0x8c, 0x77, 0x70, 0xcf, 0xa7, 0x61, 0xd6, 0xda, 0x6c, 0x32, 0x20,
0x0a, 0x1c, 0x23, 0x8a, 0xc3, 0x4e, 0x14, 0xcb, 0x11, 0x53, 0x44, 0x3b, 0x0c, 0x88, 0x02, 0xc7,
0xa6, 0x47, 0xd4, 0xd9, 0xe5, 0x59, 0xd8, 0xcc, 0x79, 0xf8, 0xb6, 0x00, 0x63, 0x82, 0x67, 0xa4,
0x7b, 0xb4, 0xbb, 0xca, 0xf6, 0xde, 0x4c, 0xc5, 0xca, 0x75, 0x01, 0xc6, 0x04, 0x6f, 0xff, 0xad,
0x05, 0x24, 0x3d, 0x1c, 0x4f, 0x60, 0xfb, 0x7e, 0x2d, 0xbd, 0x7d, 0x0f, 0x99, 0x30, 0x4f, 0xab,
0x3f, 0x60, 0x17, 0xff, 0x45, 0x0b, 0x26, 0xcd, 0xb3, 0x13, 0x52, 0xcf, 0x18, 0xa2, 0xcd, 0xb4,
0x21, 0x7a, 0x70, 0xb8, 0xf0, 0xa3, 0xfd, 0x2e, 0x3c, 0xd6, 0xbd, 0x38, 0x68, 0x47, 0x6f, 0xa7,
0x7e, 0xdd, 0xf3, 0x29, 0xcf, 0x0c, 0x8a, 0x33, 0x97, 0xd4, 0xc1, 0xcc, 0x4a, 0x50, 0xa5, 0x8f,
0x60, 0xc9, 0xec, 0x3b, 0x30, 0xdb, 0x53, 0xa6, 0x74, 0x0c, 0xa3, 0x73, 0x64, 0x15, 0xa8, 0x8d,
0x30, 0xc1, 0x18, 0x6f, 0xb6, 0xc5, 0xe1, 0xc8, 0x0a, 0xcc, 0x8a, 0x6a, 0x2b, 0x26, 0x69, 0xdb,
0x6d, 0xd0, 0x96, 0x2a, 0x3d, 0xe3, 0x81, 0xe2, 0xed, 0x2c, 0x12, 0x7b, 0xe9, 0xed, 0xcf, 0x5a,
0x30, 0x95, 0xaa, 0x1c, 0xcb, 0xc9, 0x3c, 0xf2, 0x95, 0x16, 0xf0, 0xa3, 0xbc, 0xd0, 0xf3, 0x45,
0xae, 0xaf, 0x64, 0xac, 0x34, 0x8d, 0x42, 0x93, 0xce, 0xfe, 0x62, 0x01, 0x4a, 0x49, 0x56, 0xf8,
0x18, 0xaa, 0x7c, 0xda, 0x82, 0x29, 0x15, 0x9c, 0x73, 0x97, 0x5d, 0x4c, 0xc6, 0x1b, 0xc3, 0xe7,
0xa5, 0xd5, 0x79, 0x2f, 0x73, 0xd9, 0x55, 0xec, 0x80, 0xa6, 0x30, 0x4c, 0xcb, 0x26, 0xb7, 0x01,
0xa2, 0x6e, 0x14, 0xd3, 0x96, 0x11, 0x3c, 0xd8, 0xc6, 0x8a, 0x5b, 0x74, 0x83, 0x90, 0xb2, 0xf5,
0x75, 0x23, 0xa8, 0xd2, 0x6d, 0x45, 0xa9, 0x8d, 0xab, 0x86, 0xa1, 0xc1, 0xc9, 0xfe, 0xb5, 0x02,
0xcc, 0x64, 0x55, 0x22, 0xef, 0x87, 0xc9, 0x44, 0xba, 0x71, 0x77, 0x34, 0x49, 0x85, 0x4f, 0xa2,
0x81, 0x7b, 0x70, 0xb8, 0xb0, 0xd0, 0x7b, 0x79, 0x76, 0xd1, 0x24, 0xc1, 0x14, 0x33, 0x91, 0x21,
0x91, 0xa9, 0xbc, 0x4a, 0x77, 0xb9, 0xdd, 0x96, 0x69, 0x0e, 0x23, 0x43, 0x62, 0x62, 0x31, 0x43,
0x4d, 0xb6, 0xe0, 0x8c, 0x01, 0xb9, 0x41, 0xbd, 0x7a, 0x63, 0x37, 0x08, 0xc5, 0x15, 0x83, 0x62,
0xe5, 0x19, 0xc9, 0xe5, 0x0c, 0xf6, 0xa1, 0xc1, 0xbe, 0x2d, 0x99, 0xd3, 0xe2, 0x3a, 0x6d, 0xc7,
0xf5, 0xe2, 0xae, 0x8c, 0x86, 0x94, 0x6d, 0x5a, 0x91, 0x70, 0x54, 0x14, 0xf6, 0x06, 0x8c, 0x1c,
0x73, 0x06, 0x1d, 0x6b, 0xaf, 0xbf, 0x09, 0x25, 0xc6, 0x8e, 0xd9, 0xa2, 0xbc, 0x58, 0x06, 0x50,
0x4a, 0x6e, 0x9c, 0x10, 0x1b, 0x8a, 0x9e, 0x93, 0x24, 0xa1, 0x54, 0xb7, 0xd6, 0xa2, 0xa8, 0xc3,
0x3d, 0x19, 0x86, 0x24, 0xcf, 0x42, 0x91, 0x1e, 0xb4, 0xb3, 0xd9, 0xa6, 0x4b, 0x07, 0x6d, 0x2f,
0xa4, 0x11, 0x23, 0xa2, 0x07, 0x6d, 0x32, 0x0f, 0x05, 0xaf, 0x2a, 0x37, 0x29, 0x90, 0x34, 0x85,
0xb5, 0x55, 0x2c, 0x78, 0x55, 0xfb, 0x00, 0xca, 0xea, 0x8a, 0x0b, 0xd9, 0x4b, 0x6c, 0xb7, 0x95,
0xc7, 0x31, 0x4e, 0xc2, 0x77, 0x80, 0xd5, 0xee, 0x00, 0xe8, 0x3a, 0xbd, 0xbc, 0xec, 0xcb, 0x05,
0x18, 0x71, 0x03, 0x59, 0xde, 0x5b, 0xd2, 0x6c, 0xb8, 0xd1, 0xe6, 0x18, 0xfb, 0x0e, 0x4c, 0x5f,
0xf7, 0x83, 0x7b, 0x3e, 0xdb, 0x4c, 0x2f, 0x7b, 0xb4, 0x59, 0x65, 0x8c, 0x6b, 0xec, 0x8f, 0xac,
0x8b, 0xc0, 0xb1, 0x28, 0x70, 0xea, 0x1e, 0x48, 0x61, 0xd0, 0x3d, 0x10, 0xfb, 0xe3, 0x16, 0xcc,
0xa8, 0x02, 0xb2, 0xc4, 0x1a, 0xbf, 0x08, 0x93, 0xbb, 0x1d, 0xaf, 0x59, 0x95, 0xbf, 0xa5, 0x08,
0x55, 0x22, 0x57, 0x31, 0x70, 0x98, 0xa2, 0x64, 0xee, 0xd6, 0xae, 0xe7, 0x3b, 0x61, 0x77, 0x4b,
0x9b, 0x7f, 0x65, 0x11, 0x2a, 0x0a, 0x83, 0x06, 0x95, 0xfd, 0xa7, 0x45, 0xd0, 0xd7, 0x5b, 0x88,
0x27, 0x2b, 0x21, 0xac, 0x3c, 0x72, 0x55, 0xdb, 0x5d, 0xdf, 0xd5, 0x17, 0x69, 0x4a, 0x99, 0x42,
0x88, 0x4f, 0x59, 0xcc, 0xd1, 0xf3, 0x62, 0xcf, 0xe1, 0xeb, 0x53, 0x46, 0x47, 0x5b, 0x39, 0x1d,
0x96, 0xaf, 0x09, 0xce, 0x41, 0x68, 0xba, 0x8e, 0x4a, 0x18, 0x9a, 0x92, 0xc9, 0xab, 0xf2, 0x78,
0xa1, 0x98, 0x5b, 0x1d, 0x4d, 0x29, 0x73, 0xa6, 0xd0, 0x86, 0xd1, 0x90, 0xc6, 0x61, 0x52, 0xc1,
0x74, 0x7d, 0xd8, 0xc3, 0xd6, 0x38, 0xec, 0x6e, 0xc7, 0x2c, 0x02, 0xab, 0x1b, 0xfe, 0x0d, 0x07,
0xa3, 0x10, 0x64, 0x47, 0x40, 0x7a, 0xc7, 0xe2, 0x84, 0xa9, 0xdb, 0x25, 0x28, 0x3b, 0x9d, 0x38,
0x68, 0xb1, 0x61, 0xe2, 0x9f, 0xa7, 0x64, 0x24, 0xa7, 0x13, 0x04, 0x6a, 0x1a, 0xfb, 0xf3, 0xa3,
0x90, 0x29, 0x4d, 0x20, 0x07, 0xe6, 0xd5, 0x2c, 0x2b, 0xdf, 0xab, 0x59, 0x4a, 0x99, 0x7e, 0xd7,
0xb3, 0x48, 0x1d, 0x46, 0xdb, 0x0d, 0x27, 0x4a, 0x96, 0xdf, 0xcd, 0x64, 0x98, 0xb6, 0x18, 0xf0,
0xc1, 0xe1, 0xc2, 0x7b, 0x8f, 0xe7, 0xce, 0xb1, 0xb9, 0xba, 0x24, 0xea, 0x34, 0xb5, 0x68, 0xce,
0x03, 0x05, 0x7f, 0xd3, 0xa1, 0x2b, 0x1e, 0x11, 0x9a, 0x7e, 0xc2, 0x12, 0xf5, 0x6c, 0x48, 0xa3,
0x4e, 0x33, 0x96, 0xb3, 0xe1, 0x66, 0x8e, 0xab, 0x4c, 0x30, 0xd6, 0x85, 0x6d, 0xe2, 0x37, 0x1a,
0x42, 0xc9, 0xfb, 0xa1, 0x1c, 0xc5, 0x4e, 0x18, 0x3f, 0x62, 0x19, 0x8c, 0x1a, 0xf4, 0xed, 0x84,
0x09, 0x6a, 0x7e, 0xe4, 0x15, 0x80, 0x9a, 0xe7, 0x7b, 0x51, 0xe3, 0x11, 0x4f, 0x05, 0xb9, 0xe2,
0x97, 0x15, 0x07, 0x34, 0xb8, 0x31, 0xeb, 0xc6, 0xe7, 0xb6, 0xc8, 0x63, 0x96, 0xf8, 0xf6, 0xa5,
0xac, 0x1b, 0x2a, 0x0c, 0x1a, 0x54, 0xf6, 0x47, 0xe1, 0x74, 0xf6, 0x62, 0xb5, 0x8c, 0xf0, 0xea,
0x61, 0xd0, 0x69, 0x67, 0xcd, 0x37, 0xbf, 0x78, 0x8b, 0x02, 0xc7, 0xcc, 0xf7, 0x9e, 0xe7, 0x57,
0xb3, 0xe6, 0xfb, 0xba, 0xe7, 0x57, 0x91, 0x63, 0x8e, 0x71, 0x67, 0xed, 0x77, 0x2d, 0xb8, 0x70,
0xd4, 0xfd, 0x6f, 0x16, 0xbd, 0xdf, 0x73, 0x42, 0x5f, 0x5e, 0x87, 0xe1, 0xb6, 0xe3, 0x8e, 0x13,
0xfa, 0xc8, 0xa1, 0xa4, 0x0b, 0x63, 0xa2, 0xf4, 0x4f, 0x3a, 0xa4, 0x37, 0xf3, 0xbd, 0x8d, 0xce,
0x42, 0x24, 0x95, 0x74, 0x11, 0x65, 0x87, 0x28, 0x05, 0xda, 0xdf, 0xb5, 0x80, 0x6c, 0xee, 0xd3,
0x30, 0xf4, 0xaa, 0x46, 0xb1, 0x22, 0x79, 0x01, 0x26, 0xef, 0x6e, 0x6f, 0xde, 0xd8, 0x0a, 0x3c,
0x9f, 0xdf, 0xc7, 0x30, 0x4a, 0x64, 0xae, 0x19, 0x70, 0x4c, 0x51, 0xb1, 0x20, 0xe3, 0xee, 0x6b,
0x6c, 0xcb, 0xb9, 0x74, 0xd0, 0x0e, 0x69, 0x14, 0xa9, 0x37, 0x1c, 0x64, 0x90, 0x71, 0xed, 0x66,
0x06, 0x89, 0xbd, 0xf4, 0x64, 0x13, 0xce, 0xb6, 0x78, 0x02, 0xae, 0xca, 0x77, 0xda, 0x48, 0x64,
0xe3, 0xc2, 0xa4, 0xe0, 0xfd, 0x0d, 0xf7, 0x0f, 0x17, 0xce, 0x6e, 0xf4, 0x23, 0xc0, 0xfe, 0xed,
0xec, 0xaf, 0x15, 0x60, 0xc2, 0x78, 0x43, 0xe1, 0x18, 0x3e, 0x45, 0xe6, 0xd9, 0x87, 0xc2, 0x31,
0x9f, 0x7d, 0x78, 0x0e, 0x4a, 0xed, 0xa0, 0xe9, 0xb9, 0x9e, 0xaa, 0xce, 0x9f, 0xe4, 0x67, 0x60,
0x12, 0x86, 0x0a, 0x4b, 0xee, 0x41, 0x59, 0x5d, 0x65, 0x96, 0xf5, 0x7a, 0x79, 0x79, 0x55, 0x6a,
0xf1, 0xea, 0x2b, 0xca, 0x5a, 0x16, 0xb1, 0x61, 0x8c, 0xcf, 0xfc, 0x24, 0xc3, 0xcf, 0x0b, 0x40,
0xf8, 0x92, 0x88, 0x50, 0x62, 0xec, 0x7f, 0x18, 0x85, 0x32, 0xd2, 0x76, 0xb0, 0x12, 0xd2, 0x6a,
0x44, 0xde, 0x08, 0xc5, 0x4e, 0xd8, 0x94, 0x83, 0xa5, 0xd2, 0x3f, 0xb7, 0x70, 0x1d, 0x19, 0x3c,
0xb5, 0xdd, 0x14, 0x4e, 0x74, 0x52, 0x58, 0x3c, 0xf2, 0xa4, 0xf0, 0x25, 0x98, 0x8a, 0xa2, 0xc6,
0x56, 0xe8, 0xed, 0x3b, 0x31, 0x9b, 0xc4, 0x32, 0x57, 0xa2, 0x8f, 0x66, 0xb6, 0xaf, 0x6a, 0x24,
0xa6, 0x69, 0xc9, 0x15, 0x98, 0xd5, 0xe7, 0x75, 0x34, 0x8c, 0x79, 0x6a, 0x44, 0x64, 0x51, 0xd4,
0xc9, 0x88, 0x3e, 0xe1, 0x93, 0x04, 0xd8, 0xdb, 0x86, 0xac, 0xc2, 0x4c, 0x0a, 0xc8, 0x14, 0x11,
0x29, 0x16, 0x55, 0x0b, 0x90, 0xe2, 0xc3, 0x74, 0xe9, 0x69, 0x41, 0x36, 0xe0, 0xb4, 0xf8, 0xbe,
0xfc, 0x0a, 0xbc, 0xea, 0xd1, 0x38, 0x67, 0xf4, 0xbf, 0x24, 0xa3, 0xd3, 0x57, 0x7a, 0x49, 0xb0,
0x5f, 0x3b, 0x36, 0x43, 0x15, 0x78, 0x6d, 0x55, 0x5a, 0x4a, 0x35, 0x43, 0x15, 0x9b, 0xb5, 0x2a,
0x9a, 0x74, 0xe4, 0x7d, 0xf0, 0xb4, 0xfe, 0x29, 0x32, 0x6b, 0xc2, 0x7d, 0x58, 0x95, 0xa5, 0x10,
0xea, 0xa6, 0xd1, 0x95, 0xbe, 0x64, 0x55, 0x1c, 0xd4, 0x9e, 0xec, 0xc2, 0xbc, 0x42, 0x5d, 0x62,
0xe6, 0xa0, 0x1d, 0x7a, 0x11, 0xad, 0x38, 0x11, 0xbd, 0x15, 0x36, 0x79, 0xf1, 0x44, 0x59, 0x3f,
0x04, 0x71, 0xc5, 0x8b, 0xaf, 0xf6, 0xa3, 0xc4, 0x75, 0x7c, 0x08, 0x17, 0xe6, 0xad, 0x50, 0xdf,
0xd9, 0x6d, 0xd2, 0xcd, 0x95, 0x35, 0x5e, 0x52, 0x61, 0x78, 0x2b, 0x97, 0x12, 0x04, 0x6a, 0x1a,
0xe5, 0x9e, 0x4f, 0x0e, 0x74, 0xcf, 0xbf, 0x63, 0xc1, 0x94, 0x9a, 0xec, 0x4f, 0x20, 0x0f, 0xd6,
0x4c, 0xe7, 0xc1, 0xae, 0x0c, 0xeb, 0x26, 0x4a, 0xcd, 0x07, 0x04, 0x53, 0xdf, 0x2f, 0x03, 0xf0,
0xa7, 0x75, 0x3c, 0x5e, 0xaa, 0x7b, 0x01, 0x46, 0x42, 0xda, 0x0e, 0xb2, 0x96, 0x8f, 0xe7, 0xf0,
0x39, 0xe6, 0x07, 0x77, 0x39, 0xf7, 0x3b, 0x39, 0x1e, 0xfd, 0xef, 0x3d, 0x39, 0xde, 0x86, 0xb3,
0x9e, 0x1f, 0x51, 0xb7, 0x13, 0xca, 0x9d, 0xf3, 0x6a, 0x10, 0x29, 0xeb, 0x50, 0xaa, 0xbc, 0x51,
0x32, 0x3a, 0xbb, 0xd6, 0x8f, 0x08, 0xfb, 0xb7, 0x65, 0x43, 0x9a, 0x20, 0xe4, 0x9d, 0x20, 0x1d,
0xe2, 0x4b, 0x38, 0x2a, 0x0a, 0xbd, 0x20, 0xd6, 0x6b, 0xc9, 0xa5, 0x9f, 0xcc, 0x82, 0x58, 0xbf,
0xbc, 0x8d, 0x9a, 0xa6, 0xbf, 0x55, 0x2c, 0xe7, 0x64, 0x15, 0xe1, 0xc4, 0x56, 0x31, 0x59, 0x9f,
0x13, 0x03, 0x9f, 0x51, 0x48, 0x36, 0xeb, 0xc9, 0x81, 0x9b, 0xf5, 0xcb, 0x30, 0xed, 0xf9, 0x0d,
0x1a, 0x7a, 0x31, 0xad, 0xf2, 0xb5, 0x30, 0x37, 0xc5, 0x07, 0x42, 0x65, 0x9f, 0xd6, 0x52, 0x58,
0xcc, 0x50, 0xa7, 0x8d, 0xca, 0xf4, 0x31, 0x8c, 0xca, 0x00, 0x53, 0x7e, 0x2a, 0x1f, 0x53, 0x3e,
0x33, 0xbc, 0x29, 0x9f, 0x7d, 0xac, 0xa6, 0x9c, 0xe4, 0x62, 0xca, 0x9f, 0x85, 0xd1, 0x76, 0x18,
0x1c, 0x74, 0xe7, 0x4e, 0xa7, 0xdd, 0xf3, 0x2d, 0x06, 0x44, 0x81, 0x33, 0x0b, 0xe8, 0xce, 0x3c,
0xbc, 0x80, 0xce, 0x7e, 0xbd, 0x00, 0x67, 0xb5, 0xa5, 0x63, 0xf3, 0xcb, 0xab, 0xb1, 0xb5, 0xce,
0x6f, 0x66, 0x8a, 0xa2, 0x0d, 0x23, 0xf1, 0xa9, 0x73, 0xa8, 0x0a, 0x83, 0x06, 0x15, 0xcf, 0x1f,
0xd2, 0x90, 0x97, 0xfd, 0x66, 0xcd, 0xe0, 0x8a, 0x84, 0xa3, 0xa2, 0xe0, 0xef, 0xf2, 0xd1, 0x30,
0x96, 0x67, 0x32, 0xd9, 0x8a, 0xa6, 0x15, 0x8d, 0x42, 0x93, 0x8e, 0xb9, 0x8b, 0x6e, 0xb2, 0x04,
0x99, 0x29, 0x9c, 0x14, 0xee, 0xa2, 0x5a, 0x75, 0x0a, 0x9b, 0xa8, 0xc3, 0x13, 0xc5, 0xa3, 0xbd,
0xea, 0xf0, 0x2c, 0x84, 0xa2, 0xb0, 0xff, 0xcd, 0x82, 0x37, 0xf4, 0x1d, 0x8a, 0x27, 0xb0, 0xbd,
0x1d, 0xa4, 0xb7, 0xb7, 0xed, 0xe1, 0xb7, 0xb7, 0x9e, 0x5e, 0x0c, 0xd8, 0xea, 0xfe, 0xdc, 0x82,
0x69, 0x4d, 0xff, 0x04, 0xba, 0xea, 0xe5, 0xfa, 0xc2, 0x9e, 0x56, 0x5d, 0x94, 0xa3, 0xa6, 0xfa,
0xf6, 0x1d, 0xde, 0x37, 0x11, 0xcc, 0x2d, 0xbb, 0xc9, 0x03, 0x34, 0x47, 0x04, 0x31, 0x5d, 0x18,
0xe3, 0x57, 0xf8, 0xa3, 0x7c, 0x82, 0xca, 0xb4, 0x7c, 0x7e, 0x02, 0xa4, 0x83, 0x4a, 0xfe, 0x33,
0x42, 0x29, 0x90, 0x17, 0xa5, 0x7b, 0x11, 0xb3, 0x97, 0x55, 0x99, 0x72, 0xd5, 0x45, 0xe9, 0x12,
0x8e, 0x8a, 0xc2, 0x6e, 0xc1, 0x5c, 0x9a, 0xf9, 0x2a, 0xad, 0xf1, 0xdc, 0xdd, 0xb1, 0xba, 0xb9,
0x04, 0x65, 0x87, 0xb7, 0x5a, 0xef, 0x38, 0xd9, 0x57, 0x68, 0x96, 0x13, 0x04, 0x6a, 0x1a, 0xfb,
0x57, 0x2d, 0x38, 0xdd, 0xa7, 0x33, 0x39, 0xa6, 0x9a, 0x63, 0x6d, 0x05, 0x06, 0xbc, 0x0c, 0x54,
0xa5, 0x35, 0x27, 0xc9, 0x0e, 0x19, 0x56, 0x6d, 0x55, 0x80, 0x31, 0xc1, 0xdb, 0xff, 0x68, 0xc1,
0xa9, 0xb4, 0xae, 0x11, 0xb9, 0x06, 0x44, 0x74, 0x66, 0xd5, 0x8b, 0xdc, 0x60, 0x9f, 0x86, 0x5d,
0xd6, 0x73, 0xa1, 0xf5, 0xbc, 0xe4, 0x44, 0x96, 0x7b, 0x28, 0xb0, 0x4f, 0x2b, 0x5e, 0xfb, 0x5b,
0x55, 0xa3, 0x9d, 0xcc, 0x94, 0xdb, 0x79, 0xce, 0x14, 0xfd, 0x31, 0xcd, 0x08, 0x5a, 0x89, 0x44,
0x53, 0xbe, 0xfd, 0xdd, 0x11, 0x50, 0x67, 0x51, 0x3c, 0x0f, 0x91, 0x53, 0x16, 0x27, 0xf5, 0x54,
0x51, 0xf1, 0x04, 0x4f, 0x15, 0x8d, 0x3c, 0x2c, 0x47, 0x20, 0xde, 0xcd, 0xd1, 0xbe, 0xa8, 0x61,
0xf4, 0x77, 0x34, 0x0a, 0x4d, 0x3a, 0xa6, 0x49, 0xd3, 0xdb, 0xa7, 0xa2, 0xd1, 0x58, 0x5a, 0x93,
0xf5, 0x04, 0x81, 0x9a, 0x86, 0x69, 0x52, 0xf5, 0x6a, 0x35, 0x19, 0x29, 0x2a, 0x4d, 0xd8, 0xe8,
0x20, 0xc7, 0x30, 0x8a, 0x46, 0x10, 0xec, 0x49, 0xff, 0x4f, 0x51, 0x5c, 0x0d, 0x82, 0x3d, 0xe4,
0x18, 0xe6, 0xb1, 0xf8, 0x41, 0xd8, 0x72, 0x9a, 0xde, 0x87, 0x68, 0x55, 0x49, 0x91, 0x7e, 0x9f,
0xf2, 0x58, 0x6e, 0xf4, 0x92, 0x60, 0xbf, 0x76, 0x6c, 0x06, 0xb6, 0x43, 0x5a, 0xf5, 0xdc, 0xd8,
0xe4, 0x06, 0xe9, 0x19, 0xb8, 0xd5, 0x43, 0x81, 0x7d, 0x5a, 0x91, 0x65, 0x38, 0x95, 0x9c, 0x25,
0x26, 0x35, 0x24, 0xc2, 0x19, 0x54, 0x7e, 0x38, 0xa6, 0xd1, 0x98, 0xa5, 0x67, 0xd6, 0xa6, 0x25,
0x2b, 0x79, 0xb8, 0x9b, 0x68, 0x58, 0x9b, 0xa4, 0xc2, 0x07, 0x15, 0x85, 0xfd, 0x89, 0x22, 0xdb,
0x1d, 0x07, 0xdc, 0xce, 0x7d, 0x62, 0x59, 0xc3, 0xf4, 0x8c, 0x1c, 0x39, 0xc6, 0x8c, 0x7c, 0x01,
0x26, 0xef, 0x46, 0x81, 0xaf, 0x32, 0x72, 0xa3, 0x03, 0x33, 0x72, 0x06, 0x55, 0xff, 0x8c, 0xdc,
0x58, 0x5e, 0x19, 0xb9, 0xf1, 0x47, 0xcc, 0xc8, 0xfd, 0xd1, 0x28, 0x9c, 0x53, 0xe7, 0xc9, 0x34,
0xbe, 0x17, 0x84, 0x7b, 0x9e, 0x5f, 0xe7, 0x67, 0xb0, 0x5f, 0xb5, 0x60, 0x52, 0xac, 0x17, 0xf9,
0x30, 0x82, 0x38, 0x73, 0xac, 0xe5, 0x74, 0x77, 0x2d, 0x25, 0x6c, 0x71, 0xc7, 0x10, 0x94, 0x79,
0xa5, 0xc2, 0x44, 0x61, 0x4a, 0x23, 0xf2, 0x11, 0x80, 0xe4, 0xc5, 0xac, 0x5a, 0x4e, 0xef, 0x86,
0x25, 0xfa, 0x21, 0xad, 0x69, 0xdf, 0x74, 0x47, 0x09, 0x41, 0x43, 0x20, 0x79, 0xdd, 0x52, 0x77,
0x45, 0xc4, 0x69, 0xd6, 0xab, 0x8f, 0x65, 0x6c, 0x8e, 0x73, 0x75, 0x04, 0x61, 0xdc, 0xf3, 0xeb,
0x6c, 0x9e, 0xc8, 0x24, 0xe6, 0x5b, 0xfa, 0xd5, 0x2f, 0xac, 0x07, 0x4e, 0xb5, 0xe2, 0x34, 0x1d,
0xdf, 0xa5, 0xe1, 0x9a, 0x20, 0x37, 0x9f, 0x4d, 0xe2, 0x00, 0x4c, 0x18, 0xf5, 0x5c, 0xce, 0x1c,
0x3d, 0xce, 0xe5, 0xcc, 0xf9, 0xf7, 0xc0, 0x6c, 0xcf, 0xc7, 0x3c, 0xd1, 0xd5, 0x91, 0x47, 0xbf,
0x75, 0x62, 0xff, 0xde, 0x98, 0xde, 0xb4, 0x6e, 0x04, 0x55, 0x71, 0x45, 0x30, 0xd4, 0x5f, 0x54,
0xfa, 0x9e, 0x39, 0x4e, 0x11, 0xe3, 0xe9, 0x25, 0x05, 0x44, 0x53, 0x24, 0x9b, 0xa3, 0x6d, 0x27,
0xa4, 0xfe, 0xe3, 0x9e, 0xa3, 0x5b, 0x4a, 0x08, 0x1a, 0x02, 0x49, 0x23, 0x75, 0xdc, 0x7a, 0x79,
0xf8, 0xe3, 0x56, 0xe6, 0x0e, 0xf7, 0xbd, 0xca, 0xf5, 0x05, 0x0b, 0xa6, 0xfd, 0xd4, 0xcc, 0x95,
0x47, 0x6e, 0x3b, 0x8f, 0x63, 0x55, 0x88, 0xab, 0xd9, 0x69, 0x18, 0x66, 0xe4, 0xf7, 0xdb, 0xd2,
0x46, 0x4f, 0xb8, 0xa5, 0xe9, 0xbb, 0xc6, 0x63, 0x83, 0xee, 0x1a, 0x13, 0x5f, 0xbd, 0x32, 0x30,
0x9e, 0xfb, 0x2b, 0x03, 0xd0, 0xe7, 0x85, 0x81, 0x3b, 0x50, 0x76, 0x43, 0xea, 0xc4, 0x8f, 0x78,
0xe1, 0x9c, 0x3f, 0x76, 0xb7, 0x92, 0x30, 0x40, 0xcd, 0xcb, 0xfe, 0x93, 0x22, 0xcc, 0x24, 0x23,
0x92, 0x1c, 0x45, 0xb1, 0xfd, 0x51, 0xc8, 0xd5, 0xce, 0xad, 0xda, 0x1f, 0xaf, 0x26, 0x08, 0xd4,
0x34, 0xcc, 0x1f, 0xeb, 0x44, 0x74, 0xb3, 0x4d, 0xfd, 0x75, 0x6f, 0x37, 0xe2, 0x23, 0x6e, 0x94,
0x90, 0xdd, 0xd2, 0x28, 0x34, 0xe9, 0x98, 0x33, 0x2e, 0xfc, 0xe2, 0x28, 0x7b, 0xb2, 0x2b, 0xfd,
0x6d, 0x4c, 0xf0, 0xe4, 0x2b, 0x7d, 0x9f, 0x0b, 0xc9, 0xa7, 0xa6, 0xa1, 0xe7, 0x04, 0xee, 0x84,
0xef, 0x84, 0x7c, 0xde, 0x82, 0x53, 0x7b, 0xa9, 0xfa, 0x95, 0xc4, 0x24, 0x0f, 0x59, 0x69, 0x99,
0x2e, 0x8a, 0xd1, 0x53, 0x38, 0x0d, 0x8f, 0x30, 0x2b, 0xdd, 0xfe, 0x17, 0x0b, 0x4c, 0xf3, 0x74,
0x3c, 0xcf, 0xca, 0x78, 0x00, 0xaa, 0x70, 0xc4, 0x03, 0x50, 0x89, 0x13, 0x56, 0x3c, 0x9e, 0xd3,
0x3f, 0x72, 0x02, 0xa7, 0x7f, 0x74, 0xa0, 0xd7, 0xf6, 0x46, 0x28, 0x76, 0xbc, 0xaa, 0xf4, 0xdb,
0xf5, 0x61, 0xd8, 0xda, 0x2a, 0x32, 0xb8, 0xfd, 0x3b, 0xa3, 0x3a, 0x4e, 0x97, 0x47, 0xf1, 0x3f,
0x14, 0xdd, 0xae, 0xa9, 0xc2, 0x59, 0xd1, 0xf3, 0x1b, 0x3d, 0x85, 0xb3, 0xef, 0x3e, 0x79, 0xa5,
0x85, 0x18, 0xa0, 0x41, 0x75, 0xb3, 0xe3, 0x47, 0x94, 0x59, 0xdc, 0x85, 0x12, 0x0b, 0x6d, 0x78,
0xc2, 0xad, 0x94, 0x52, 0xaa, 0x74, 0x55, 0xc2, 0x1f, 0x1c, 0x2e, 0xbc, 0xeb, 0xe4, 0x6a, 0x25,
0xad, 0x51, 0xf1, 0x27, 0x11, 0x94, 0xd9, 0xdf, 0xbc, 0x22, 0x44, 0x06, 0x4d, 0xb7, 0x94, 0x2d,
0x4a, 0x10, 0xb9, 0x94, 0x9b, 0x68, 0x39, 0xc4, 0x87, 0x32, 0x7f, 0xaa, 0x88, 0x0b, 0x15, 0xb1,
0xd5, 0x96, 0xaa, 0xcb, 0x48, 0x10, 0x0f, 0x0e, 0x17, 0x5e, 0x3a, 0xb9, 0x50, 0xd5, 0x1c, 0xb5,
0x08, 0xfb, 0x6f, 0x8a, 0x7a, 0xee, 0xca, 0x7a, 0xe9, 0x1f, 0x8a, 0xb9, 0xfb, 0x62, 0x66, 0xee,
0x5e, 0xe8, 0x99, 0xbb, 0xd3, 0xfa, 0x39, 0x9f, 0xd4, 0x6c, 0x7c, 0xd2, 0x1b, 0xec, 0xd1, 0x71,
0x3c, 0xf7, 0x2c, 0x5e, 0xeb, 0x78, 0x21, 0x8d, 0xb6, 0xc2, 0x8e, 0xef, 0xf9, 0x75, 0xf9, 0xa8,
0xa3, 0xe1, 0x59, 0xa4, 0xd0, 0x98, 0xa5, 0xb7, 0xbf, 0xc6, 0xcf, 0x3b, 0x8d, 0xe2, 0x32, 0xf6,
0x95, 0x9b, 0xfc, 0xb5, 0x27, 0x51, 0x51, 0xaa, 0xbe, 0xb2, 0x78, 0xe2, 0x49, 0xe0, 0xc8, 0x3d,
0x18, 0xdf, 0x15, 0x2f, 0x4e, 0xe4, 0x73, 0xc5, 0x49, 0x3e, 0x5f, 0xc1, 0x2f, 0x93, 0x26, 0x6f,
0x59, 0x3c, 0xd0, 0x7f, 0x62, 0x22, 0xcd, 0xfe, 0xf9, 0x22, 0x9c, 0xca, 0xbc, 0x45, 0xc4, 0x02,
0xfe, 0xe4, 0xe1, 0xa9, 0x6c, 0x76, 0x5e, 0x3d, 0x6a, 0xac, 0x28, 0xc8, 0x07, 0x01, 0xaa, 0xb4,
0xdd, 0x0c, 0xba, 0xdc, 0x71, 0x19, 0x39, 0xb1, 0xe3, 0xa2, 0x7c, 0xdd, 0x55, 0xc5, 0x05, 0x0d,
0x8e, 0xb2, 0x8c, 0x76, 0x54, 0xbc, 0xa7, 0x91, 0x2e, 0xa3, 0x35, 0x6e, 0xfa, 0x8d, 0x3d, 0xd9,
0x9b, 0x7e, 0x1e, 0x9c, 0x12, 0x2a, 0xaa, 0x12, 0xae, 0x47, 0xa8, 0xd4, 0x3a, 0xcd, 0x66, 0xd4,
0x6a, 0x9a, 0x0d, 0x66, 0xf9, 0xda, 0x9f, 0x2b, 0x30, 0xf7, 0x4d, 0x0c, 0xf6, 0x46, 0x92, 0x1c,
0x7f, 0x33, 0x8c, 0x39, 0x9d, 0xb8, 0x11, 0xf4, 0xbc, 0x00, 0xb2, 0xcc, 0xa1, 0x28, 0xb1, 0x64,
0x1d, 0x46, 0xaa, 0x4e, 0x9c, 0x3c, 0xca, 0x7f, 0x12, 0xe5, 0x74, 0x26, 0xcc, 0x89, 0x29, 0x72,
0x2e, 0xe4, 0x19, 0x18, 0x89, 0x9d, 0x7a, 0xea, 0x05, 0xcf, 0x1d, 0xa7, 0x1e, 0x21, 0x87, 0x9a,
0xbb, 0xcb, 0xc8, 0x11, 0xbb, 0xcb, 0x4b, 0xc6, 0x3f, 0x9c, 0x30, 0x4e, 0x5d, 0x7a, 0xff, 0x49,
0x84, 0x28, 0xec, 0x4f, 0xd1, 0xda, 0xff, 0x0f, 0x26, 0xcd, 0x7f, 0x22, 0x71, 0xac, 0xbb, 0x46,
0xf6, 0xdf, 0x8f, 0xc0, 0x54, 0xaa, 0xcc, 0x2f, 0x35, 0xcb, 0xad, 0x23, 0x67, 0x39, 0x3f, 0x4f,
0xeb, 0xf8, 0x54, 0x16, 0x71, 0x1a, 0xe7, 0x69, 0x1d, 0x9f, 0xa2, 0xc0, 0xb1, 0xaf, 0x52, 0x0d,
0xbb, 0xd8, 0xf1, 0x65, 0x56, 0x5e, 0x7d, 0x95, 0x55, 0x0e, 0x45, 0x89, 0x65, 0x01, 0xec, 0x64,
0xc4, 0x8d, 0xa2, 0xb0, 0x11, 0x72, 0xd5, 0x5c, 0xcb, 0xe3, 0xd5, 0x34, 0x59, 0xd2, 0xca, 0x03,
0x7a, 0x13, 0x82, 0x29, 0x89, 0xe4, 0x93, 0x96, 0xf9, 0x5e, 0xdc, 0x58, 0x1e, 0xa7, 0x49, 0xd9,
0x2a, 0x4a, 0xb1, 0x82, 0x1e, 0xfe, 0x6c, 0x5c, 0xa4, 0x16, 0xf0, 0xf8, 0xe3, 0x59, 0xc0, 0xd0,
0x67, 0xf1, 0xbe, 0x15, 0xca, 0x2d, 0xc7, 0xf7, 0x6a, 0x34, 0x8a, 0xc5, 0x3f, 0x80, 0x29, 0x8b,
0xe8, 0x69, 0x23, 0x01, 0xa2, 0xc6, 0xf3, 0x7f, 0xb3, 0xc4, 0x3b, 0x26, 0x82, 0x98, 0xb2, 0xf1,
0x6f, 0x96, 0x34, 0x18, 0x4d, 0x1a, 0xfb, 0x37, 0x2c, 0x38, 0xdb, 0x77, 0x30, 0x7e, 0x70, 0xd3,
0x9f, 0xf6, 0x6f, 0x15, 0xe0, 0x74, 0x9f, 0x32, 0x58, 0xd2, 0x7d, 0x6c, 0xcf, 0x0a, 0xca, 0x3a,
0xdb, 0xa9, 0x81, 0x73, 0xe3, 0x64, 0xdb, 0x90, 0xde, 0x0a, 0x8a, 0x4f, 0x74, 0x2b, 0xb0, 0xbf,
0x56, 0x00, 0xe3, 0x01, 0x4c, 0xf2, 0x51, 0xb3, 0xe2, 0xdb, 0xca, 0xab, 0x3a, 0x59, 0x30, 0x57,
0x15, 0xe3, 0x62, 0xd4, 0xfa, 0x15, 0x90, 0x67, 0xe7, 0x6b, 0xe1, 0xe8, 0xf9, 0x4a, 0x9a, 0x49,
0x69, 0x7d, 0x31, 0xff, 0xd2, 0xfa, 0x72, 0x4f, 0x59, 0xfd, 0xcf, 0x5a, 0x62, 0xa6, 0x65, 0xba,
0xa4, 0x2d, 0xac, 0xf5, 0x10, 0x0b, 0xfb, 0x36, 0x28, 0x45, 0xb4, 0x59, 0x63, 0x9e, 0x9d, 0xb4,
0xc4, 0xfa, 0xbd, 0x6d, 0x09, 0x47, 0x45, 0xc1, 0xef, 0xce, 0x36, 0x9b, 0xc1, 0xbd, 0x4b, 0xad,
0x76, 0xdc, 0x95, 0x36, 0x59, 0xdf, 0x9d, 0x55, 0x18, 0x34, 0xa8, 0xec, 0x7f, 0xb5, 0xc4, 0xe7,
0x94, 0x3e, 0xfa, 0x8b, 0x99, 0x3b, 0x8d, 0xc7, 0x77, 0x6f, 0x7f, 0x12, 0xc0, 0x55, 0x6f, 0x12,
0xe4, 0xf3, 0x2e, 0xa6, 0x7e, 0xe3, 0xc0, 0x7c, 0xac, 0x31, 0x81, 0xa1, 0x21, 0x2f, 0xb5, 0x78,
0x8a, 0x47, 0x2d, 0x1e, 0xfb, 0x9f, 0x2c, 0x48, 0x6d, 0x16, 0xa4, 0x0d, 0xa3, 0x4c, 0x83, 0x6e,
0x3e, 0x2f, 0x28, 0x98, 0xac, 0xd9, 0xc2, 0x92, 0xd3, 0x82, 0xff, 0x89, 0x42, 0x10, 0x69, 0x4a,
0xef, 0xbc, 0x90, 0xc7, 0x2b, 0x1f, 0xa6, 0x40, 0xe6, 0xdf, 0xcb, 0x7f, 0x88, 0xa1, 0x3c, 0x7d,
0xfb, 0x45, 0x98, 0xed, 0x51, 0x8a, 0xdf, 0x48, 0x0a, 0x92, 0x67, 0x23, 0x8c, 0x19, 0xc8, 0xef,
0x47, 0xa2, 0xc0, 0x31, 0x07, 0x7f, 0x26, 0xcb, 0x9e, 0x7c, 0xd9, 0x82, 0xd9, 0x28, 0xcb, 0xef,
0x71, 0x8d, 0x9d, 0xca, 0x5c, 0xf5, 0xa0, 0xb0, 0x57, 0x09, 0xfb, 0x3f, 0xa5, 0x79, 0x12, 0xff,
0x82, 0x4c, 0x6d, 0x2e, 0xd6, 0xc0, 0xcd, 0x85, 0x2d, 0x31, 0xb7, 0x41, 0xab, 0x9d, 0x66, 0x4f,
0x6d, 0xce, 0xb6, 0x84, 0xa3, 0xa2, 0x48, 0xbd, 0x8f, 0x57, 0x3c, 0xf2, 0x7d, 0xbc, 0x17, 0x60,
0xd2, 0x7c, 0x1a, 0x85, 0xa7, 0xd0, 0xe4, 0xe1, 0x83, 0xf9, 0x8a, 0x0a, 0xa6, 0xa8, 0x32, 0xef,
0xab, 0x8d, 0x1e, 0xf9, 0xbe, 0xda, 0x73, 0x50, 0x92, 0x6f, 0x85, 0x25, 0xf9, 0x5d, 0x51, 0xf8,
0x23, 0x61, 0xa8, 0xb0, 0xcc, 0x40, 0xb4, 0x1c, 0xbf, 0xe3, 0x34, 0xd9, 0x08, 0xc9, 0x7a, 0x40,
0xb5, 0xb2, 0x36, 0x14, 0x06, 0x0d, 0x2a, 0xd6, 0xe3, 0xd8, 0x6b, 0xd1, 0x57, 0x02, 0x3f, 0xc9,
0x8c, 0xa8, 0x1e, 0xef, 0x48, 0x38, 0x2a, 0x0a, 0xfb, 0xef, 0x2c, 0xc8, 0x3e, 0x74, 0x94, 0xaa,
0x41, 0xb4, 0x8e, 0xac, 0x41, 0x4c, 0xd7, 0x57, 0x15, 0x8e, 0x55, 0x5f, 0x65, 0x96, 0x3e, 0x15,
0x1f, 0x5a, 0xfa, 0xf4, 0x26, 0x7d, 0xaf, 0x5d, 0xd4, 0x48, 0x4d, 0xf4, 0xbb, 0xd3, 0x4e, 0x6c,
0x18, 0x73, 0x1d, 0x55, 0xe2, 0x3d, 0x29, 0xdc, 0xaa, 0x95, 0x65, 0x4e, 0x24, 0x31, 0x95, 0xdd,
0x6f, 0x7c, 0xef, 0xfc, 0x53, 0xdf, 0xfc, 0xde, 0xf9, 0xa7, 0xbe, 0xfd, 0xbd, 0xf3, 0x4f, 0x7d,
0xfc, 0xfe, 0x79, 0xeb, 0x1b, 0xf7, 0xcf, 0x5b, 0xdf, 0xbc, 0x7f, 0xde, 0xfa, 0xf6, 0xfd, 0xf3,
0xd6, 0x77, 0xef, 0x9f, 0xb7, 0xbe, 0xf0, 0xd7, 0xe7, 0x9f, 0x7a, 0xe5, 0xdd, 0xc3, 0xfc, 0xcf,
0xdb, 0xff, 0x0a, 0x00, 0x00, 0xff, 0xff, 0xb9, 0x8e, 0x56, 0xe9, 0x32, 0x77, 0x00, 0x00,
}
func (m *AWSAuthConfig) Marshal() (dAtA []byte, err error) {
@ -3186,6 +3187,15 @@ func (m *AppProjectSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if len(m.SourceNamespaces) > 0 {
for iNdEx := len(m.SourceNamespaces) - 1; iNdEx >= 0; iNdEx-- {
i -= len(m.SourceNamespaces[iNdEx])
copy(dAtA[i:], m.SourceNamespaces[iNdEx])
i = encodeVarintGenerated(dAtA, i, uint64(len(m.SourceNamespaces[iNdEx])))
i--
dAtA[i] = 0x62
}
}
if len(m.ClusterResourceBlacklist) > 0 {
for iNdEx := len(m.ClusterResourceBlacklist) - 1; iNdEx >= 0; iNdEx-- {
{
@ -8235,6 +8245,12 @@ func (m *AppProjectSpec) Size() (n int) {
n += 1 + l + sovGenerated(uint64(l))
}
}
if len(m.SourceNamespaces) > 0 {
for _, s := range m.SourceNamespaces {
l = len(s)
n += 1 + l + sovGenerated(uint64(l))
}
}
return n
}
@ -10132,6 +10148,7 @@ func (this *AppProjectSpec) String() string {
`NamespaceResourceWhitelist:` + repeatedStringForNamespaceResourceWhitelist + `,`,
`SignatureKeys:` + repeatedStringForSignatureKeys + `,`,
`ClusterResourceBlacklist:` + repeatedStringForClusterResourceBlacklist + `,`,
`SourceNamespaces:` + fmt.Sprintf("%v", this.SourceNamespaces) + `,`,
`}`,
}, "")
return s
@ -12284,6 +12301,38 @@ func (m *AppProjectSpec) Unmarshal(dAtA []byte) error {
return err
}
iNdEx = postIndex
case 12:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field SourceNamespaces", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.SourceNamespaces = append(m.SourceNamespaces, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])

View file

@ -82,6 +82,9 @@ message AppProjectSpec {
// ClusterResourceBlacklist contains list of blacklisted cluster level resources
repeated k8s.io.apimachinery.pkg.apis.meta.v1.GroupKind clusterResourceBlacklist = 11;
// SourceNamespaces defines the namespaces application resources are allowed to be created in
repeated string sourceNamespaces = 12;
}
// AppProjectStatus contains status information for AppProject CRs

View file

@ -377,6 +377,21 @@ func schema_pkg_apis_application_v1alpha1_AppProjectSpec(ref common.ReferenceCal
},
},
},
"sourceNamespaces": {
SchemaProps: spec.SchemaProps{
Description: "SourceNamespaces defines the namespaces application resources are allowed to be created in",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
},
},

View file

@ -1669,6 +1669,8 @@ type AppProjectSpec struct {
SignatureKeys []SignatureKey `json:"signatureKeys,omitempty" protobuf:"bytes,10,opt,name=signatureKeys"`
// ClusterResourceBlacklist contains list of blacklisted cluster level resources
ClusterResourceBlacklist []metav1.GroupKind `json:"clusterResourceBlacklist,omitempty" protobuf:"bytes,11,opt,name=clusterResourceBlacklist"`
// SourceNamespaces defines the namespaces application resources are allowed to be created in
SourceNamespaces []string `json:"sourceNamespaces,omitempty" protobuf:"bytes,12,opt,name=sourceNamespaces"`
}
// SyncWindows is a collection of sync windows in this project
@ -2520,3 +2522,37 @@ func (d *ApplicationDestination) MarshalJSON() ([]byte, error) {
}
return json.Marshal(&struct{ *Alias }{Alias: (*Alias)(dest)})
}
// InstanceName returns the name of the application as used in the instance
// tracking values, i.e. in the format <namespace>_<name>. When the namespace
// of the application is similar to the value of defaultNs, only the name of
// the application is returned to keep backwards compatibility.
func (a *Application) InstanceName(defaultNs string) string {
// When app has no namespace set, or the namespace is the default ns, we
// return just the application name
if a.Namespace == "" || a.Namespace == defaultNs {
return a.Name
}
return a.Namespace + "_" + a.Name
}
// QualifiedName returns the full qualified name of the application, including
// the name of the namespace it is created in delimited by a forward slash,
// i.e. <namespace>/<appname>
func (a *Application) QualifiedName() string {
if a.Namespace == "" {
return a.Name
} else {
return a.Namespace + "/" + a.Name
}
}
// RBACName returns the full qualified RBAC resource name for the application
// in a backwards-compatible way.
func (a *Application) RBACName(defaultNS string) string {
if defaultNS != "" && a.Namespace != defaultNS && a.Namespace != "" {
return fmt.Sprintf("%s/%s/%s", a.Spec.GetProject(), a.Namespace, a.Name)
} else {
return fmt.Sprintf("%s/%s", a.Spec.GetProject(), a.Name)
}
}

View file

@ -2883,3 +2883,160 @@ func TestGetCAPath(t *testing.T) {
assert.Empty(t, path)
}
}
func TestAppProjectIsSourceNamespacePermitted(t *testing.T) {
app1 := &Application{
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "argocd",
},
Spec: ApplicationSpec{},
}
app2 := &Application{
ObjectMeta: metav1.ObjectMeta{
Name: "app2",
Namespace: "some-ns",
},
Spec: ApplicationSpec{},
}
app3 := &Application{
ObjectMeta: metav1.ObjectMeta{
Name: "app2",
Namespace: "",
},
Spec: ApplicationSpec{},
}
app4 := &Application{
ObjectMeta: metav1.ObjectMeta{
Name: "app2",
Namespace: "other-ns",
},
Spec: ApplicationSpec{},
}
app5 := &Application{
ObjectMeta: metav1.ObjectMeta{
Name: "app2",
Namespace: "some-ns1",
},
Spec: ApplicationSpec{},
}
app6 := &Application{
ObjectMeta: metav1.ObjectMeta{
Name: "app2",
Namespace: "some-ns2",
},
Spec: ApplicationSpec{},
}
app7 := &Application{
ObjectMeta: metav1.ObjectMeta{
Name: "app2",
Namespace: "someotherns",
},
Spec: ApplicationSpec{},
}
t.Run("App in same namespace as controller", func(t *testing.T) {
proj := &AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "argocd",
},
Spec: AppProjectSpec{
SourceNamespaces: []string{"other-ns"},
},
}
// app1 is installed to argocd namespace, controller as well
assert.True(t, proj.IsAppNamespacePermitted(app1, "argocd"))
// app2 is installed to some-ns namespace, controller as well
assert.True(t, proj.IsAppNamespacePermitted(app2, "some-ns"))
// app3 has no namespace set, so will be implicitly created in controller's namespace
assert.True(t, proj.IsAppNamespacePermitted(app3, "argocd"))
})
t.Run("App not permitted when sourceNamespaces is empty", func(t *testing.T) {
proj := &AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "argocd",
},
Spec: AppProjectSpec{
SourceNamespaces: []string{},
},
}
// app1 is installed to argocd namespace
assert.True(t, proj.IsAppNamespacePermitted(app1, "argocd"))
// app2 is installed to some-ns, controller running in argocd
assert.False(t, proj.IsAppNamespacePermitted(app2, "argocd"))
})
t.Run("App permitted when sourceNamespaces has app namespace", func(t *testing.T) {
proj := &AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "argocd",
},
Spec: AppProjectSpec{
SourceNamespaces: []string{"some-ns"},
},
}
// app2 is installed to some-ns, controller running in argocd
assert.True(t, proj.IsAppNamespacePermitted(app2, "argocd"))
// app4 is installed to other-ns, controller running in argocd
assert.False(t, proj.IsAppNamespacePermitted(app4, "argocd"))
})
t.Run("App permitted by glob pattern", func(t *testing.T) {
proj := &AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "argocd",
},
Spec: AppProjectSpec{
SourceNamespaces: []string{"some-*"},
},
}
// app5 is installed to some-ns1, controller running in argocd
assert.True(t, proj.IsAppNamespacePermitted(app5, "argocd"))
// app6 is installed to some-ns2, controller running in argocd
assert.True(t, proj.IsAppNamespacePermitted(app6, "argocd"))
// app7 is installed to someotherns, controller running in argocd
assert.False(t, proj.IsAppNamespacePermitted(app7, "argocd"))
})
}
func Test_RBACName(t *testing.T) {
testApp := func(namespace, project string) *Application {
return &Application{
ObjectMeta: metav1.ObjectMeta{
Name: "test-app",
Namespace: namespace,
},
Spec: ApplicationSpec{
Project: project,
},
}
}
t.Run("App in same namespace as controller when ns is argocd", func(t *testing.T) {
a := testApp("argocd", "default")
assert.Equal(t, "default/test-app", a.RBACName("argocd"))
})
t.Run("App in same namespace as controller when ns is not argocd", func(t *testing.T) {
a := testApp("some-ns", "default")
assert.Equal(t, "default/test-app", a.RBACName("some-ns"))
})
t.Run("App in different namespace as controller when ns is argocd", func(t *testing.T) {
a := testApp("some-ns", "default")
assert.Equal(t, "default/some-ns/test-app", a.RBACName("argocd"))
})
t.Run("App in different namespace as controller when ns is not argocd", func(t *testing.T) {
a := testApp("some-ns", "default")
assert.Equal(t, "default/some-ns/test-app", a.RBACName("other-ns"))
})
t.Run("App in same namespace as controller when project is not yet set", func(t *testing.T) {
a := testApp("argocd", "")
assert.Equal(t, "default/test-app", a.RBACName("argocd"))
})
t.Run("App in same namespace as controller when ns is not yet set", func(t *testing.T) {
a := testApp("", "")
assert.Equal(t, "default/test-app", a.RBACName("argocd"))
})
}

View file

@ -149,6 +149,11 @@ func (in *AppProjectSpec) DeepCopyInto(out *AppProjectSpec) {
*out = make([]v1.GroupKind, len(*in))
copy(*out, *in)
}
if in.SourceNamespaces != nil {
in, out := &in.SourceNamespaces, &out.SourceNamespaces
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}

View file

@ -752,8 +752,14 @@ func helmTemplate(appPath string, repoRoot string, env *v1alpha1.Env, q *apiclie
defer manifestGenerateLock.Unlock(appPath)
}
// We use the app name as Helm's release name property, which must not
// contain any underscore characters and must not exceed 53 characters.
// We are not interested in the fully qualified application name while
// templating, thus, we just use the name part of the identifier.
appName, _ := argo.ParseAppInstanceName(q.AppName, "")
templateOpts := &helm.TemplateOpts{
Name: q.AppName,
Name: appName,
Namespace: q.Namespace,
KubeVersion: text.SemVer(q.KubeVersion),
APIVersions: q.ApiVersions,

View file

@ -44,12 +44,12 @@ import (
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
servercache "github.com/argoproj/argo-cd/v2/server/cache"
"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
apputil "github.com/argoproj/argo-cd/v2/util/app"
"github.com/argoproj/argo-cd/v2/util/argo"
argoutil "github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v2/util/env"
"github.com/argoproj/argo-cd/v2/util/git"
"github.com/argoproj/argo-cd/v2/util/glob"
"github.com/argoproj/argo-cd/v2/util/io"
ioutil "github.com/argoproj/argo-cd/v2/util/io"
"github.com/argoproj/argo-cd/v2/util/lua"
@ -72,21 +72,22 @@ var (
// Server provides an Application service
type Server struct {
ns string
kubeclientset kubernetes.Interface
appclientset appclientset.Interface
appLister applisters.ApplicationNamespaceLister
appInformer cache.SharedIndexInformer
appBroadcaster *broadcasterHandler
repoClientset apiclient.Clientset
kubectl kube.Kubectl
db db.ArgoDB
enf *rbac.Enforcer
projectLock sync.KeyLock
auditLogger *argo.AuditLogger
settingsMgr *settings.SettingsManager
cache *servercache.Cache
projInformer cache.SharedIndexInformer
ns string
kubeclientset kubernetes.Interface
appclientset appclientset.Interface
appLister applisters.ApplicationLister
appInformer cache.SharedIndexInformer
appBroadcaster *broadcasterHandler
repoClientset apiclient.Clientset
kubectl kube.Kubectl
db db.ArgoDB
enf *rbac.Enforcer
projectLock sync.KeyLock
auditLogger *argo.AuditLogger
settingsMgr *settings.SettingsManager
cache *servercache.Cache
projInformer cache.SharedIndexInformer
enabledNamespaces []string
}
// NewServer returns a new instance of the Application service
@ -94,7 +95,7 @@ func NewServer(
namespace string,
kubeclientset kubernetes.Interface,
appclientset appclientset.Interface,
appLister applisters.ApplicationNamespaceLister,
appLister applisters.ApplicationLister,
appInformer cache.SharedIndexInformer,
repoClientset apiclient.Clientset,
cache *servercache.Cache,
@ -104,25 +105,27 @@ func NewServer(
projectLock sync.KeyLock,
settingsMgr *settings.SettingsManager,
projInformer cache.SharedIndexInformer,
enabledNamespaces []string,
) (application.ApplicationServiceServer, AppResourceTreeFn) {
appBroadcaster := &broadcasterHandler{}
appInformer.AddEventHandler(appBroadcaster)
s := &Server{
ns: namespace,
appclientset: appclientset,
appLister: appLister,
appInformer: appInformer,
appBroadcaster: appBroadcaster,
kubeclientset: kubeclientset,
cache: cache,
db: db,
repoClientset: repoClientset,
kubectl: kubectl,
enf: enf,
projectLock: projectLock,
auditLogger: argo.NewAuditLogger(namespace, kubeclientset, "argocd-server"),
settingsMgr: settingsMgr,
projInformer: projInformer,
ns: namespace,
appclientset: appclientset,
appLister: appLister,
appInformer: appInformer,
appBroadcaster: appBroadcaster,
kubeclientset: kubeclientset,
cache: cache,
db: db,
repoClientset: repoClientset,
kubectl: kubectl,
enf: enf,
projectLock: projectLock,
auditLogger: argo.NewAuditLogger(namespace, kubeclientset, "argocd-server"),
settingsMgr: settingsMgr,
projInformer: projInformer,
enabledNamespaces: enabledNamespaces,
}
return s, s.GetAppResources
}
@ -133,16 +136,27 @@ func (s *Server) List(ctx context.Context, q *application.ApplicationQuery) (*ap
if err != nil {
return nil, fmt.Errorf("error converting selector to labels map: %w", err)
}
apps, err := s.appLister.List(labelsMap.AsSelector())
var apps []*appv1.Application
if q.GetAppNamespace() == "" {
apps, err = s.appLister.List(labelsMap.AsSelector())
} else {
apps, err = s.appLister.Applications(q.GetAppNamespace()).List(labelsMap.AsSelector())
}
if err != nil {
return nil, fmt.Errorf("error listing apps with selectors: %w", err)
}
newItems := make([]appv1.Application, 0)
for _, a := range apps {
if s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)) {
// Skip any application that is neither in the conrol plane's namespace
// nor in the list of enabled namespaces.
if a.Namespace != s.ns && !glob.MatchStringInList(s.enabledNamespaces, a.Namespace, false) {
continue
}
if s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)) {
newItems = append(newItems, *a)
}
}
if q.Name != nil {
newItems, err = argoutil.FilterByName(newItems, *q.Name)
if err != nil {
@ -175,14 +189,15 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
if q.GetApplication() == nil {
return nil, fmt.Errorf("error creating application: application is nil in request")
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionCreate, apputil.AppRBACName(*q.Application)); err != nil {
a := q.GetApplication()
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionCreate, a.RBACName(s.ns)); err != nil {
return nil, err
}
s.projectLock.RLock(q.Application.Spec.Project)
defer s.projectLock.RUnlock(q.Application.Spec.Project)
s.projectLock.RLock(a.Spec.GetProject())
defer s.projectLock.RUnlock(a.Spec.GetProject())
a := q.GetApplication()
validate := true
if q.Validate != nil {
validate = *q.Validate
@ -191,7 +206,14 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
if err != nil {
return nil, fmt.Errorf("error while validating and normalizing app: %w", err)
}
created, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Create(ctx, a, metav1.CreateOptions{})
appNs := s.appNamespaceOrDefault(a.Namespace)
if !s.isNamespaceEnabled(appNs) {
return nil, namespaceNotPermittedError(appNs)
}
created, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Create(ctx, a, metav1.CreateOptions{})
if err == nil {
s.logAppEvent(created, ctx, argo.EventReasonResourceCreated, "created application")
s.waitSync(created)
@ -200,10 +222,11 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
if !apierr.IsAlreadyExists(err) {
return nil, fmt.Errorf("error creating application: %w", err)
}
// act idempotent if existing spec matches new spec
existing, err := s.appLister.Get(a.Name)
existing, err := s.appLister.Applications(appNs).Get(a.Name)
if err != nil {
return nil, status.Errorf(codes.Internal, "unable to check existing application details: %v", err)
return nil, status.Errorf(codes.Internal, "unable to check existing application details (%s): %v", appNs, err)
}
equalSpecs := reflect.DeepEqual(existing.Spec, a.Spec) &&
reflect.DeepEqual(existing.Labels, a.Labels) &&
@ -216,7 +239,7 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
if q.Upsert == nil || !*q.Upsert {
return nil, status.Errorf(codes.InvalidArgument, "existing application spec is different, use upsert flag to force update")
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, a.RBACName(s.ns)); err != nil {
return nil, err
}
updated, err := s.updateApp(existing, a, ctx, true)
@ -253,7 +276,7 @@ func (s *Server) queryRepoServer(ctx context.Context, a *v1alpha1.Application, a
if err != nil {
return fmt.Errorf("error getting kustomize settings options: %w", err)
}
proj, err := argo.GetAppProject(&a.Spec, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
if err != nil {
if apierr.IsNotFound(err) {
return status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", a.Spec.Project)
@ -291,14 +314,23 @@ func (s *Server) queryRepoServer(ctx context.Context, a *v1alpha1.Application, a
// GetManifests returns application manifests
func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationManifestQuery) (*apiclient.ManifestResponse, error) {
a, err := s.appLister.Get(*q.Name)
if q.Name == nil || *q.Name == "" {
return nil, fmt.Errorf("invalid request: application name is missing")
}
appName := q.GetName()
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
a, err := s.appLister.Applications(appNs).Get(appName)
if err != nil {
return nil, fmt.Errorf("error getting application: %w", err)
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
return nil, err
}
if !s.isNamespaceEnabled(a.Namespace) {
return nil, namespaceNotPermittedError(a.Namespace)
}
var manifestInfo *apiclient.ManifestResponse
err = s.queryRepoServer(ctx, a, func(
client apiclient.RepoServerServiceClient, repo *appv1.Repository, helmRepos []*appv1.Repository, helmCreds []*appv1.RepoCreds, helmOptions *appv1.HelmOptions, kustomizeOptions *appv1.KustomizeOptions, enableGenerateManifests map[string]bool) error {
@ -334,7 +366,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
Repo: repo,
Revision: revision,
AppLabelKey: appInstanceLabelKey,
AppName: a.Name,
AppName: a.InstanceName(s.ns),
Namespace: a.Spec.Destination.Namespace,
ApplicationSource: &a.Spec.Source,
Repos: helmRepos,
@ -381,17 +413,20 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
// Get returns an application by name
func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*appv1.Application, error) {
appName := q.GetName()
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
// We must use a client Get instead of an informer Get, because it's common to call Get immediately
// following a Watch (which is not yet powered by an informer), and the Get must reflect what was
// previously seen by the client.
a, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, q.GetName(), metav1.GetOptions{
a, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Get(ctx, appName, metav1.GetOptions{
ResourceVersion: q.GetResourceVersion(),
})
if err != nil {
return nil, fmt.Errorf("error getting application: %w", err)
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
return nil, err
}
if q.Refresh == nil {
@ -402,16 +437,16 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
if *q.Refresh == string(appv1.RefreshTypeHard) {
refreshType = appv1.RefreshTypeHard
}
appIf := s.appclientset.ArgoprojV1alpha1().Applications(s.ns)
appIf := s.appclientset.ArgoprojV1alpha1().Applications(appNs)
// subscribe early with buffered channel to ensure we don't miss events
events := make(chan *appv1.ApplicationWatchEvent, watchAPIBufferSize)
unsubscribe := s.appBroadcaster.Subscribe(events, func(event *appv1.ApplicationWatchEvent) bool {
return event.Application.Name == q.GetName()
return event.Application.Name == appName && event.Application.Namespace == appNs
})
defer unsubscribe()
app, err := argoutil.RefreshApp(appIf, *q.Name, refreshType)
app, err := argoutil.RefreshApp(appIf, appName, refreshType)
if err != nil {
return nil, fmt.Errorf("error refreshing the app: %w", err)
}
@ -430,7 +465,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
_, err := client.GetAppDetails(ctx, &apiclient.RepoServerAppDetailsQuery{
Repo: repo,
Source: &app.Spec.Source,
AppName: app.Name,
AppName: appName,
KustomizeOptions: kustomizeOptions,
Repos: helmRepos,
NoCache: true,
@ -469,11 +504,13 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
// ListResourceEvents returns a list of event resources
func (s *Server) ListResourceEvents(ctx context.Context, q *application.ApplicationResourceEventsQuery) (*v1.EventList, error) {
a, err := s.appLister.Get(*q.Name)
appName := q.GetName()
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
a, err := s.appLister.Applications(appNs).Get(appName)
if err != nil {
return nil, fmt.Errorf("error getting application: %w", err)
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
return nil, err
}
var (
@ -537,7 +574,7 @@ func (s *Server) validateAndUpdateApp(ctx context.Context, newApp *appv1.Applica
s.projectLock.RLock(newApp.Spec.GetProject())
defer s.projectLock.RUnlock(newApp.Spec.GetProject())
app, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, newApp.Name, metav1.GetOptions{})
app, err := s.appclientset.ArgoprojV1alpha1().Applications(newApp.Namespace).Get(ctx, newApp.Name, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error getting application: %w", err)
}
@ -586,7 +623,7 @@ func (s *Server) waitSync(app *appv1.Application) {
return
}
for {
if currApp, err := s.appLister.Get(app.Name); err == nil {
if currApp, err := s.appLister.Applications(app.Namespace).Get(app.Name); err == nil {
currVersion, err := strconv.Atoi(currApp.ResourceVersion)
if err == nil && currVersion >= minVersion {
return
@ -613,7 +650,7 @@ func (s *Server) updateApp(app *appv1.Application, newApp *appv1.Application, ct
app.Finalizers = newApp.Finalizers
res, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Update(ctx, app, metav1.UpdateOptions{})
res, err := s.appclientset.ArgoprojV1alpha1().Applications(app.Namespace).Update(ctx, app, metav1.UpdateOptions{})
if err == nil {
s.logAppEvent(app, ctx, argo.EventReasonResourceUpdated, "updated application spec")
s.waitSync(res)
@ -623,7 +660,7 @@ func (s *Server) updateApp(app *appv1.Application, newApp *appv1.Application, ct
return nil, err
}
app, err = s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, newApp.Name, metav1.GetOptions{})
app, err = s.appclientset.ArgoprojV1alpha1().Applications(app.Namespace).Get(ctx, newApp.Name, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error getting application: %w", err)
}
@ -633,7 +670,11 @@ func (s *Server) updateApp(app *appv1.Application, newApp *appv1.Application, ct
// Update updates an application
func (s *Server) Update(ctx context.Context, q *application.ApplicationUpdateRequest) (*appv1.Application, error) {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, apputil.AppRBACName(*q.Application)); err != nil {
if q.GetApplication() == nil {
return nil, fmt.Errorf("error creating application: application is nil in request")
}
a := q.GetApplication()
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, a.RBACName(s.ns)); err != nil {
return nil, err
}
@ -649,11 +690,13 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
if q.GetSpec() == nil {
return nil, fmt.Errorf("error updating application spec: spec is nil in request")
}
a, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, *q.Name, metav1.GetOptions{})
appName := q.GetName()
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
a, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Get(ctx, appName, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error getting application: %w", err)
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, a.RBACName(s.ns)); err != nil {
return nil, err
}
a.Spec = *q.GetSpec()
@ -670,13 +713,14 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
// Patch patches an application
func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchRequest) (*appv1.Application, error) {
app, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, *q.Name, metav1.GetOptions{})
appName := q.GetName()
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
app, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Get(ctx, appName, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error getting application: %w", err)
}
if err = s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, apputil.AppRBACName(*app)); err != nil {
if err = s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, app.RBACName(s.ns)); err != nil {
return nil, err
}
@ -716,7 +760,9 @@ func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchReque
// Delete removes an application and all associated resources
func (s *Server) Delete(ctx context.Context, q *application.ApplicationDeleteRequest) (*application.ApplicationResponse, error) {
a, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, *q.Name, metav1.GetOptions{})
appName := q.GetName()
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
a, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Get(ctx, appName, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error getting application: %w", err)
}
@ -724,7 +770,7 @@ func (s *Server) Delete(ctx context.Context, q *application.ApplicationDeleteReq
s.projectLock.RLock(a.Spec.Project)
defer s.projectLock.RUnlock(a.Spec.Project)
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionDelete, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionDelete, a.RBACName(s.ns)); err != nil {
return nil, err
}
@ -768,7 +814,7 @@ func (s *Server) Delete(ctx context.Context, q *application.ApplicationDeleteReq
}
}
err = s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Delete(ctx, *q.Name, metav1.DeleteOptions{})
err = s.appclientset.ArgoprojV1alpha1().Applications(appNs).Delete(ctx, appName, metav1.DeleteOptions{})
if err != nil {
return nil, fmt.Errorf("error deleting application: %w", err)
}
@ -777,6 +823,8 @@ func (s *Server) Delete(ctx context.Context, q *application.ApplicationDeleteReq
}
func (s *Server) Watch(q *application.ApplicationQuery, ws application.ApplicationService_WatchServer) error {
appName := q.GetName()
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
logCtx := log.NewEntry(log.New())
if q.Name != nil {
logCtx = logCtx.WithField("application", *q.Name)
@ -807,12 +855,12 @@ func (s *Server) Watch(q *application.ApplicationQuery, ws application.Applicati
if appVersion, err := strconv.Atoi(a.ResourceVersion); err == nil && appVersion < minVersion {
return
}
matchedEvent := (q.GetName() == "" || a.Name == q.GetName()) && selector.Matches(labels.Set(a.Labels))
matchedEvent := (appName == "" || (a.Name == appName && a.Namespace == appNs)) && selector.Matches(labels.Set(a.Labels))
if !matchedEvent {
return
}
if !s.enf.Enforce(claims, rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(a)) {
if !s.enf.Enforce(claims, rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)) {
// do not emit apps user does not have accessing
return
}
@ -837,7 +885,7 @@ func (s *Server) Watch(q *application.ApplicationQuery, ws application.Applicati
return fmt.Errorf("error listing apps with selector: %w", err)
}
sort.Slice(apps, func(i, j int) bool {
return apps[i].Name < apps[j].Name
return apps[i].QualifiedName() < apps[j].QualifiedName()
})
for i := range apps {
sendIfPermitted(*apps[i], watch.Added)
@ -856,14 +904,18 @@ func (s *Server) Watch(q *application.ApplicationQuery, ws application.Applicati
}
func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Application, validate bool) error {
proj, err := argo.GetAppProject(&app.Spec, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
proj, err := argo.GetAppProject(app, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
if err != nil {
if apierr.IsNotFound(err) {
return status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", app.Spec.Project)
}
return fmt.Errorf("error getting application's project: %w", err)
}
currApp, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, app.Name, metav1.GetOptions{})
if app.GetName() == "" {
return fmt.Errorf("resource name may not be empty")
}
appNs := s.appNamespaceOrDefault(app.Namespace)
currApp, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Get(ctx, app.Name, metav1.GetOptions{})
if err != nil {
if !apierr.IsNotFound(err) {
return fmt.Errorf("error getting application by name: %w", err)
@ -875,11 +927,11 @@ func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Applica
if currApp != nil && currApp.Spec.GetProject() != app.Spec.GetProject() {
// When changing projects, caller must have application create & update privileges in new project
// NOTE: the update check was already verified in the caller to this function
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionCreate, apputil.AppRBACName(*app)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionCreate, app.RBACName(s.ns)); err != nil {
return err
}
// They also need 'update' privileges in the old project
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, apputil.AppRBACName(*currApp)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, currApp.RBACName(s.ns)); err != nil {
return err
}
}
@ -949,8 +1001,9 @@ func (s *Server) getCachedAppState(ctx context.Context, a *appv1.Application, ge
return errors.New(argoutil.FormatAppConditions(conditions))
}
_, err = s.Get(ctx, &application.ApplicationQuery{
Name: pointer.StringPtr(a.Name),
Refresh: pointer.StringPtr(string(appv1.RefreshTypeNormal)),
Name: pointer.StringPtr(a.GetName()),
AppNamespace: pointer.StringPtr(a.GetNamespace()),
Refresh: pointer.StringPtr(string(appv1.RefreshTypeNormal)),
})
if err != nil {
return fmt.Errorf("error getting application by query: %w", err)
@ -963,7 +1016,7 @@ func (s *Server) getCachedAppState(ctx context.Context, a *appv1.Application, ge
func (s *Server) GetAppResources(ctx context.Context, a *appv1.Application) (*appv1.ApplicationTree, error) {
var tree appv1.ApplicationTree
err := s.getCachedAppState(ctx, a, func() error {
return s.cache.GetAppResourcesTree(a.Name, &tree)
return s.cache.GetAppResourcesTree(a.InstanceName(s.ns), &tree)
})
if err != nil {
return &tree, fmt.Errorf("error getting cached app state: %w", err)
@ -972,11 +1025,13 @@ func (s *Server) GetAppResources(ctx context.Context, a *appv1.Application) (*ap
}
func (s *Server) getAppLiveResource(ctx context.Context, action string, q *application.ApplicationResourceRequest) (*appv1.ResourceNode, *rest.Config, *appv1.Application, error) {
a, err := s.appLister.Get(*q.Name)
appName := q.GetName()
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
a, err := s.appLister.Applications(appNs).Get(appName)
if err != nil {
return nil, nil, nil, fmt.Errorf("error getting app by name: %w", err)
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, action, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, action, a.RBACName(s.ns)); err != nil {
return nil, nil, nil, err
}
@ -1037,6 +1092,7 @@ func replaceSecretValues(obj *unstructured.Unstructured) (*unstructured.Unstruct
func (s *Server) PatchResource(ctx context.Context, q *application.ApplicationResourcePatchRequest) (*application.ApplicationResourceResponse, error) {
resourceRequest := &application.ApplicationResourceRequest{
Name: q.Name,
AppNamespace: q.AppNamespace,
Namespace: q.Namespace,
ResourceName: q.ResourceName,
Kind: q.Kind,
@ -1047,7 +1103,7 @@ func (s *Server) PatchResource(ctx context.Context, q *application.ApplicationRe
if err != nil {
return nil, fmt.Errorf("error getting app live resource: %w", err)
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionUpdate, a.RBACName(s.ns)); err != nil {
return nil, err
}
@ -1078,6 +1134,7 @@ func (s *Server) PatchResource(ctx context.Context, q *application.ApplicationRe
func (s *Server) DeleteResource(ctx context.Context, q *application.ApplicationResourceDeleteRequest) (*application.ApplicationResponse, error) {
resourceRequest := &application.ApplicationResourceRequest{
Name: q.Name,
AppNamespace: q.AppNamespace,
Namespace: q.Namespace,
ResourceName: q.ResourceName,
Kind: q.Kind,
@ -1088,7 +1145,7 @@ func (s *Server) DeleteResource(ctx context.Context, q *application.ApplicationR
if err != nil {
return nil, fmt.Errorf("error getting live resource for delete: %w", err)
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionDelete, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionDelete, a.RBACName(s.ns)); err != nil {
return nil, err
}
var deleteOption metav1.DeleteOptions
@ -1112,23 +1169,27 @@ func (s *Server) DeleteResource(ctx context.Context, q *application.ApplicationR
}
func (s *Server) ResourceTree(ctx context.Context, q *application.ResourcesQuery) (*appv1.ApplicationTree, error) {
a, err := s.appLister.Get(q.GetApplicationName())
appName := q.GetApplicationName()
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
a, err := s.appLister.Applications(appNs).Get(appName)
if err != nil {
return nil, fmt.Errorf("error getting application by name: %w", err)
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
return nil, err
}
return s.GetAppResources(ctx, a)
}
func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application.ApplicationService_WatchResourceTreeServer) error {
a, err := s.appLister.Get(q.GetApplicationName())
appName := q.GetApplicationName()
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
a, err := s.appLister.Applications(appNs).Get(appName)
if err != nil {
return fmt.Errorf("error getting application by name: %w", err)
}
if err := s.enf.EnforceErr(ws.Context().Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ws.Context().Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
return err
}
@ -1143,11 +1204,13 @@ func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application
}
func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMetadataQuery) (*v1alpha1.RevisionMetadata, error) {
a, err := s.appLister.Get(q.GetName())
appName := q.GetName()
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
a, err := s.appLister.Applications(appNs).Get(appName)
if err != nil {
return nil, fmt.Errorf("error getting app by name: %w", err)
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
return nil, err
}
repo, err := s.db.GetRepository(ctx, a.Spec.Source.RepoURL)
@ -1156,7 +1219,7 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe
}
// We need to get some information with the project associated to the app,
// so we'll know whether GPG signatures are enforced.
proj, err := argo.GetAppProject(&a.Spec, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), a.Namespace, s.settingsMgr, s.db, ctx)
proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
if err != nil {
return nil, fmt.Errorf("error getting app project: %w", err)
}
@ -1180,16 +1243,18 @@ func isMatchingResource(q *application.ResourcesQuery, key kube.ResourceKey) boo
}
func (s *Server) ManagedResources(ctx context.Context, q *application.ResourcesQuery) (*application.ManagedResourcesResponse, error) {
a, err := s.appLister.Get(*q.ApplicationName)
appName := q.GetApplicationName()
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
a, err := s.appLister.Applications(appNs).Get(appName)
if err != nil {
return nil, fmt.Errorf("error getting application: %w", err)
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
return nil, fmt.Errorf("error verifying rbac: %w", err)
}
items := make([]*appv1.ResourceDiff, 0)
err = s.getCachedAppState(ctx, a, func() error {
return s.cache.GetAppManagedResources(a.Name, &items)
return s.cache.GetAppManagedResources(a.InstanceName(s.ns), &items)
})
if err != nil {
return nil, fmt.Errorf("error getting cached app state: %w", err)
@ -1239,12 +1304,14 @@ func (s *Server) PodLogs(q *application.ApplicationPodLogsQuery, ws application.
}
}
a, err := s.appLister.Get(q.GetName())
appName := q.GetName()
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
a, err := s.appLister.Applications(appNs).Get(appName)
if err != nil {
return fmt.Errorf("error getting application by name: %w", err)
}
if err := s.enf.EnforceErr(ws.Context().Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ws.Context().Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
return err
}
@ -1259,7 +1326,7 @@ func (s *Server) PodLogs(q *application.ApplicationPodLogsQuery, ws application.
}
if serverRBACLogEnforceEnable {
if err := s.enf.EnforceErr(ws.Context().Value("claims"), rbacpolicy.ResourceLogs, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ws.Context().Value("claims"), rbacpolicy.ResourceLogs, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
return err
}
}
@ -1436,13 +1503,15 @@ func isTheSelectedOne(currentNode *appv1.ResourceNode, q *application.Applicatio
// Sync syncs an application to its target state
func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncRequest) (*appv1.Application, error) {
appIf := s.appclientset.ArgoprojV1alpha1().Applications(s.ns)
a, err := appIf.Get(ctx, *syncReq.Name, metav1.GetOptions{})
appName := syncReq.GetName()
appNs := s.appNamespaceOrDefault(syncReq.GetAppNamespace())
appIf := s.appclientset.ArgoprojV1alpha1().Applications(appNs)
a, err := appIf.Get(ctx, appName, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error getting application by name: %w", err)
}
proj, err := argo.GetAppProject(&a.Spec, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), a.Namespace, s.settingsMgr, s.db, ctx)
proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
if err != nil {
if apierr.IsNotFound(err) {
return a, status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", a.Spec.Project)
@ -1454,11 +1523,11 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
return a, status.Errorf(codes.PermissionDenied, "cannot sync: blocked by sync window")
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionSync, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionSync, a.RBACName(s.ns)); err != nil {
return nil, err
}
if syncReq.Manifests != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionOverride, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionOverride, a.RBACName(s.ns)); err != nil {
return nil, err
}
if a.Spec.SyncPolicy != nil && a.Spec.SyncPolicy.Automated != nil && !syncReq.GetDryRun() {
@ -1521,7 +1590,7 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
op.Retry = *retry
}
a, err = argo.SetAppOperation(appIf, *syncReq.Name, &op)
a, err = argo.SetAppOperation(appIf, appName, &op)
if err != nil {
return nil, fmt.Errorf("error setting app operation: %w", err)
}
@ -1538,12 +1607,14 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
}
func (s *Server) Rollback(ctx context.Context, rollbackReq *application.ApplicationRollbackRequest) (*appv1.Application, error) {
appIf := s.appclientset.ArgoprojV1alpha1().Applications(s.ns)
a, err := appIf.Get(ctx, *rollbackReq.Name, metav1.GetOptions{})
appName := rollbackReq.GetName()
appNs := s.appNamespaceOrDefault(rollbackReq.GetAppNamespace())
appIf := s.appclientset.ArgoprojV1alpha1().Applications(appNs)
a, err := appIf.Get(ctx, appName, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error getting application by name: %w", err)
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionSync, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionSync, a.RBACName(s.ns)); err != nil {
return nil, err
}
if a.DeletionTimestamp != nil {
@ -1561,7 +1632,7 @@ func (s *Server) Rollback(ctx context.Context, rollbackReq *application.Applicat
}
}
if deploymentInfo == nil {
return nil, status.Errorf(codes.InvalidArgument, "application %s does not have deployment with id %v", a.Name, rollbackReq.GetId())
return nil, status.Errorf(codes.InvalidArgument, "application %s does not have deployment with id %v", a.QualifiedName(), rollbackReq.GetId())
}
if deploymentInfo.Source.IsZero() {
// Since source type was introduced to history starting with v0.12, and is now required for
@ -1586,7 +1657,7 @@ func (s *Server) Rollback(ctx context.Context, rollbackReq *application.Applicat
},
InitiatedBy: appv1.OperationInitiator{Username: session.Username(ctx)},
}
a, err = argo.SetAppOperation(appIf, *rollbackReq.Name, &op)
a, err = argo.SetAppOperation(appIf, appName, &op)
if err != nil {
return nil, fmt.Errorf("error setting app operation: %w", err)
}
@ -1633,11 +1704,13 @@ func (s *Server) resolveRevision(ctx context.Context, app *appv1.Application, sy
}
func (s *Server) TerminateOperation(ctx context.Context, termOpReq *application.OperationTerminateRequest) (*application.OperationTerminateResponse, error) {
a, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, *termOpReq.Name, metav1.GetOptions{})
appName := termOpReq.GetName()
appNs := s.appNamespaceOrDefault(termOpReq.GetAppNamespace())
a, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Get(ctx, appName, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error getting application by name: %w", err)
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionSync, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionSync, a.RBACName(s.ns)); err != nil {
return nil, err
}
@ -1646,7 +1719,7 @@ func (s *Server) TerminateOperation(ctx context.Context, termOpReq *application.
return nil, status.Errorf(codes.InvalidArgument, "Unable to terminate operation. No operation is in progress")
}
a.Status.OperationState.Phase = common.OperationTerminating
updated, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Update(ctx, a, metav1.UpdateOptions{})
updated, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Update(ctx, a, metav1.UpdateOptions{})
if err == nil {
s.waitSync(updated)
s.logAppEvent(a, ctx, argo.EventReasonResourceUpdated, "terminated running operation")
@ -1657,7 +1730,7 @@ func (s *Server) TerminateOperation(ctx context.Context, termOpReq *application.
}
log.Warnf("failed to set operation for app %q due to update conflict. retrying again...", *termOpReq.Name)
time.Sleep(100 * time.Millisecond)
a, err = s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Get(ctx, *termOpReq.Name, metav1.GetOptions{})
a, err = s.appclientset.ArgoprojV1alpha1().Applications(appNs).Get(ctx, appName, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error getting application by name: %w", err)
}
@ -1734,6 +1807,7 @@ func (s *Server) getAvailableActions(resourceOverrides map[string]appv1.Resource
func (s *Server) RunResourceAction(ctx context.Context, q *application.ResourceActionRunRequest) (*application.ApplicationResponse, error) {
resourceRequest := &application.ApplicationResourceRequest{
Name: q.Name,
AppNamespace: q.AppNamespace,
Namespace: q.Namespace,
ResourceName: q.ResourceName,
Kind: q.Kind,
@ -1870,17 +1944,19 @@ func (s *Server) plugins() ([]*v1alpha1.ConfigManagementPlugin, error) {
}
func (s *Server) GetApplicationSyncWindows(ctx context.Context, q *application.ApplicationSyncWindowsQuery) (*application.ApplicationSyncWindowsResponse, error) {
appIf := s.appclientset.ArgoprojV1alpha1().Applications(s.ns)
a, err := appIf.Get(ctx, *q.Name, metav1.GetOptions{})
appName := q.GetName()
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
appIf := s.appclientset.ArgoprojV1alpha1().Applications(appNs)
a, err := appIf.Get(ctx, appName, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error getting application by name: %w", err)
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, apputil.AppRBACName(*a)); err != nil {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
return nil, err
}
proj, err := argo.GetAppProject(&a.Spec, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), a.Namespace, s.settingsMgr, s.db, ctx)
proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
if err != nil {
return nil, fmt.Errorf("error getting app project: %w", err)
}
@ -1928,3 +2004,19 @@ func getPropagationPolicyFinalizer(policy string) string {
return ""
}
}
func (s *Server) appNamespaceOrDefault(appNs string) string {
if appNs == "" {
return s.ns
} else {
return appNs
}
}
func (s *Server) isNamespaceEnabled(namespace string) bool {
return namespace == s.ns || glob.MatchStringInList(s.enabledNamespaces, namespace, false)
}
func namespaceNotPermittedError(namespace string) error {
return fmt.Errorf("namespace '%s' is not permitted", namespace)
}

View file

@ -27,11 +27,14 @@ message ApplicationQuery {
optional string selector = 5;
// the repoURL to restrict returned list applications
optional string repo = 6;
// the application's namespace
optional string appNamespace = 7;
}
message NodeQuery {
// the application's name
optional string name = 1;
optional string appNamespace = 2;
}
message RevisionMetadataQuery{
@ -39,6 +42,8 @@ message RevisionMetadataQuery{
required string name = 1;
// the revision of the app
required string revision = 2;
// the application's namespace
optional string appNamespace = 3;
}
// ApplicationEventsQuery is a query for application resource events
@ -47,12 +52,14 @@ message ApplicationResourceEventsQuery {
optional string resourceNamespace = 2;
optional string resourceName = 3;
optional string resourceUID = 4;
optional string appNamespace = 5;
}
// ManifestQuery is a query for manifest resources
message ApplicationManifestQuery {
required string name = 1;
optional string revision = 2;
optional string appNamespace = 3;
}
message ApplicationResponse {}
@ -72,6 +79,7 @@ message ApplicationDeleteRequest {
required string name = 1;
optional bool cascade = 2;
optional string propagationPolicy = 3;
optional string appNamespace = 4;
}
message SyncOptions {
@ -90,6 +98,7 @@ message ApplicationSyncRequest {
repeated github.com.argoproj.argo_cd.v2.pkg.apis.application.v1alpha1.Info infos = 9;
optional github.com.argoproj.argo_cd.v2.pkg.apis.application.v1alpha1.RetryStrategy retryStrategy = 10;
optional SyncOptions syncOptions = 11;
optional string appNamespace = 12;
}
// ApplicationUpdateSpecRequest is a request to update application spec
@ -97,6 +106,7 @@ message ApplicationUpdateSpecRequest {
required string name = 1;
required github.com.argoproj.argo_cd.v2.pkg.apis.application.v1alpha1.ApplicationSpec spec = 2;
optional bool validate = 3;
optional string appNamespace = 4;
}
// ApplicationPatchRequest is a request to patch an application
@ -104,6 +114,7 @@ message ApplicationPatchRequest {
required string name = 1;
required string patch = 2;
required string patchType = 3;
optional string appNamespace = 5;
}
message ApplicationRollbackRequest {
@ -111,6 +122,7 @@ message ApplicationRollbackRequest {
required int64 id = 2;
optional bool dryRun = 3;
optional bool prune = 4;
optional string appNamespace = 6;
}
message ApplicationResourceRequest {
@ -120,6 +132,7 @@ message ApplicationResourceRequest {
required string version = 4;
optional string group = 5;
required string kind = 6;
optional string appNamespace = 7;
}
message ApplicationResourcePatchRequest {
@ -131,6 +144,7 @@ message ApplicationResourcePatchRequest {
required string kind = 6;
required string patch = 7;
required string patchType = 8;
optional string appNamespace = 9;
}
message ApplicationResourceDeleteRequest {
@ -142,6 +156,7 @@ message ApplicationResourceDeleteRequest {
required string kind = 6;
optional bool force = 7;
optional bool orphan = 8;
optional string appNamespace = 9;
}
message ResourceActionRunRequest {
@ -152,6 +167,7 @@ message ResourceActionRunRequest {
optional string group = 5;
required string kind = 6;
required string action = 7;
optional string appNamespace = 8;
}
message ResourceActionsListResponse {
@ -177,6 +193,7 @@ message ApplicationPodLogsQuery {
optional string group = 12;
optional string resourceName = 13 ;
optional bool previous = 14;
optional string appNamespace = 15;
}
message LogEntry {
@ -190,10 +207,12 @@ message LogEntry {
message OperationTerminateRequest {
required string name = 1;
optional string appNamespace = 2;
}
message ApplicationSyncWindowsQuery {
required string name = 1;
optional string appNamespace = 2;
}
message ApplicationSyncWindowsResponse {
@ -221,6 +240,7 @@ message ResourcesQuery {
optional string version = 4;
optional string group = 5;
optional string kind = 6;
optional string appNamespace = 7;
}
message ManagedResourcesResponse {

View file

@ -37,6 +37,7 @@ import (
"github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
"github.com/argoproj/argo-cd/v2/test"
"github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/assets"
"github.com/argoproj/argo-cd/v2/util/cache"
"github.com/argoproj/argo-cd/v2/util/db"
@ -201,7 +202,7 @@ func newTestAppServerWithEnforcerConfigure(f func(*rbac.Enforcer), objects ...ru
testNamespace,
kubeclientset,
fakeAppsClientset,
factory.Argoproj().V1alpha1().Applications().Lister().Applications(testNamespace),
factory.Argoproj().V1alpha1().Applications().Lister(),
appInformer,
mockRepoClient,
nil,
@ -211,6 +212,7 @@ func newTestAppServerWithEnforcerConfigure(f func(*rbac.Enforcer), objects ...ru
sync.NewKeyLock(),
settingsMgr,
projInformer,
[]string{},
)
return server.(*Server)
}
@ -749,8 +751,14 @@ func TestServer_GetApplicationSyncWindowsState(t *testing.T) {
func TestGetCachedAppState(t *testing.T) {
testApp := newTestApp()
testApp.ObjectMeta.ResourceVersion = "1"
testApp.Spec.Project = "none"
appServer := newTestAppServer(testApp)
testApp.Spec.Project = "test-proj"
testProj := &appsv1.AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: "test-proj",
Namespace: testNamespace,
},
}
appServer := newTestAppServer(testApp, testProj)
fakeClientSet := appServer.appclientset.(*apps.Clientset)
t.Run("NoError", func(t *testing.T) {
err := appServer.getCachedAppState(context.Background(), testApp, func() error {
@ -901,7 +909,8 @@ func TestLogsGetSelectedPod(t *testing.T) {
// refreshAnnotationRemover runs an infinite loop until it detects and removes refresh annotation or given context is done
func refreshAnnotationRemover(t *testing.T, ctx context.Context, patched *int32, appServer *Server, appName string, ch chan string) {
for ctx.Err() == nil {
a, err := appServer.appLister.Get(appName)
aName, appNs := argo.ParseAppQualifiedName(appName, appServer.ns)
a, err := appServer.appLister.Applications(appNs).Get(aName)
require.NoError(t, err)
a = a.DeepCopy()
if a.GetAnnotations() != nil && a.GetAnnotations()[appsv1.AnnotationKeyRefresh] != "" {

View file

@ -28,16 +28,17 @@ import (
)
type terminalHandler struct {
appLister applisters.ApplicationNamespaceLister
appLister applisters.ApplicationLister
db db.ArgoDB
enf *rbac.Enforcer
cache *servercache.Cache
appResourceTreeFn func(ctx context.Context, app *appv1.Application) (*appv1.ApplicationTree, error)
allowedShells []string
namespace string
}
// NewHandler returns a new terminal handler.
func NewHandler(appLister applisters.ApplicationNamespaceLister, db db.ArgoDB, enf *rbac.Enforcer, cache *servercache.Cache,
func NewHandler(appLister applisters.ApplicationLister, namespace string, db db.ArgoDB, enf *rbac.Enforcer, cache *servercache.Cache,
appResourceTree AppResourceTreeFn, allowedShells []string) *terminalHandler {
return &terminalHandler{
appLister: appLister,
@ -46,6 +47,7 @@ func NewHandler(appLister applisters.ApplicationNamespaceLister, db db.ArgoDB, e
cache: cache,
appResourceTreeFn: appResourceTree,
allowedShells: allowedShells,
namespace: namespace,
}
}
@ -143,7 +145,7 @@ func (s *terminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fieldLog := log.WithFields(log.Fields{"application": app, "userName": sessionmgr.Username(ctx), "container": container,
"podName": podName, "namespace": namespace, "cluster": project})
a, err := s.appLister.Get(app)
a, err := s.appLister.Applications(s.namespace).Get(app)
if err != nil {
if apierr.IsNotFound(err) {
http.Error(w, "App not found", http.StatusNotFound)

View file

@ -35,7 +35,7 @@ type Server struct {
repoClientset apiclient.Clientset
enf *rbac.Enforcer
cache *servercache.Cache
appLister applisters.ApplicationNamespaceLister
appLister applisters.ApplicationLister
projLister applisters.AppProjectNamespaceLister
settings *settings.SettingsManager
}
@ -46,7 +46,7 @@ func NewServer(
db db.ArgoDB,
enf *rbac.Enforcer,
cache *servercache.Cache,
appLister applisters.ApplicationNamespaceLister,
appLister applisters.ApplicationLister,
projLister applisters.AppProjectNamespaceLister,
settings *settings.SettingsManager,
) *Server {
@ -287,8 +287,8 @@ func (s *Server) GetAppDetails(ctx context.Context, q *repositorypkg.RepoAppDeta
if err := s.enf.EnforceErr(claims, rbacpolicy.ResourceRepositories, rbacpolicy.ActionGet, createRBACObject(repo.Project, repo.Repo)); err != nil {
return nil, err
}
app, err := s.appLister.Get(q.AppName)
appName, appNs := argo.ParseAppQualifiedName(q.AppName, s.settings.GetNamespace())
app, err := s.appLister.Applications(appNs).Get(appName)
appRBACObj := createRBACObject(q.AppProject, q.AppName)
// ensure caller has read privileges to app
if err := s.enf.EnforceErr(claims, rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, appRBACObj); err != nil {

View file

@ -123,7 +123,7 @@ var (
}
)
func newAppAndProjLister(objects ...runtime.Object) (applisters.ApplicationNamespaceLister, applisters.AppProjectNamespaceLister) {
func newAppAndProjLister(objects ...runtime.Object) (applisters.ApplicationLister, applisters.AppProjectNamespaceLister) {
fakeAppsClientset := fakeapps.NewSimpleClientset(objects...)
factory := appinformer.NewSharedInformerFactoryWithOptions(fakeAppsClientset, 0, appinformer.WithNamespace(""), appinformer.WithTweakListOptions(func(options *metav1.ListOptions) {}))
projInformer := factory.Argoproj().V1alpha1().AppProjects()
@ -136,7 +136,7 @@ func newAppAndProjLister(objects ...runtime.Object) (applisters.ApplicationNames
_ = appsInformer.Informer().GetStore().Add(obj)
}
}
appLister := appsInformer.Lister().Applications(testNamespace)
appLister := appsInformer.Lister()
projLister := projInformer.Lister().AppProjects(testNamespace)
return appLister, projLister
}

View file

@ -167,7 +167,7 @@ type ArgoCDServer struct {
projLister applisters.AppProjectNamespaceLister
policyEnforcer *rbacpolicy.RBACPolicyEnforcer
appInformer cache.SharedIndexInformer
appLister applisters.ApplicationNamespaceLister
appLister applisters.ApplicationLister
db db.ArgoDB
// stopCh is the channel which when closed, will shutdown the Argo CD server
@ -200,6 +200,7 @@ type ArgoCDServerOpts struct {
XFrameOptions string
ContentSecurityPolicy string
ListenHost string
ApplicationNamespaces []string
}
// initializeDefaultProject creates the default project if it does not already exist
@ -231,12 +232,18 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
err = initializeDefaultProject(opts)
errors.CheckError(err)
factory := appinformer.NewSharedInformerFactoryWithOptions(opts.AppClientset, 0, appinformer.WithNamespace(opts.Namespace), appinformer.WithTweakListOptions(func(options *metav1.ListOptions) {}))
projInformer := factory.Argoproj().V1alpha1().AppProjects().Informer()
projLister := factory.Argoproj().V1alpha1().AppProjects().Lister().AppProjects(opts.Namespace)
appInformerNs := opts.Namespace
if len(opts.ApplicationNamespaces) > 0 {
appInformerNs = ""
}
projFactory := appinformer.NewSharedInformerFactoryWithOptions(opts.AppClientset, 0, appinformer.WithNamespace(opts.Namespace), appinformer.WithTweakListOptions(func(options *metav1.ListOptions) {}))
appFactory := appinformer.NewSharedInformerFactoryWithOptions(opts.AppClientset, 0, appinformer.WithNamespace(appInformerNs), appinformer.WithTweakListOptions(func(options *metav1.ListOptions) {}))
appInformer := factory.Argoproj().V1alpha1().Applications().Informer()
appLister := factory.Argoproj().V1alpha1().Applications().Lister().Applications(opts.Namespace)
projInformer := projFactory.Argoproj().V1alpha1().AppProjects().Informer()
projLister := projFactory.Argoproj().V1alpha1().AppProjects().Lister().AppProjects(opts.Namespace)
appInformer := appFactory.Argoproj().V1alpha1().Applications().Informer()
appLister := appFactory.Argoproj().V1alpha1().Applications().Lister()
userStateStorage := util_session.NewUserStateStorage(opts.RedisClient)
sessionMgr := util_session.NewSessionManager(settingsMgr, projLister, opts.DexServerAddr, opts.DexTLSConfig, userStateStorage)
@ -440,6 +447,7 @@ func (a *ArgoCDServer) Run(ctx context.Context, listeners *Listeners) {
// Start the muxed listeners for our servers
log.Infof("argocd %s serving on port %d (url: %s, tls: %v, namespace: %s, sso: %v)",
common.GetVersion(), a.ListenPort, a.settings.URL, a.useTLS(), a.Namespace, a.settings.IsSSOConfigured())
log.Infof("Enabled application namespace patterns: %s", a.allowedApplicationNamespacesAsString())
go func() { a.checkServeErr("grpcS", grpcS.Serve(grpcL)) }()
go func() { a.checkServeErr("httpS", httpS.Serve(httpL)) }()
@ -687,7 +695,8 @@ func (a *ArgoCDServer) newGRPCServer() (*grpc.Server, application.AppResourceTre
a.enf,
projectLock,
a.settingsMgr,
a.projInformer)
a.projInformer,
a.ApplicationNamespaces)
projectService := project.NewServer(a.Namespace, a.KubeClientset, a.AppClientset, a.enf, projectLock, a.sessionMgr, a.policyEnforcer, a.projInformer, a.settingsMgr, a.db)
settingsService := settings.NewServer(a.settingsMgr, a, a.DisableAuth)
accountService := account.NewServer(a.sessionMgr, a.settingsMgr, a.enf)
@ -812,7 +821,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
}
mux.Handle("/api/", handler)
terminalHandler := application.NewHandler(a.appLister, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells)
terminalHandler := application.NewHandler(a.appLister, a.Namespace, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells)
mux.HandleFunc("/terminal", func(writer http.ResponseWriter, request *http.Request) {
argocdSettings, err := a.settingsMgr.GetSettings()
if err != nil {
@ -1262,3 +1271,14 @@ func bug21955WorkaroundInterceptor(ctx context.Context, req interface{}, _ *grpc
}
return handler(ctx, req)
}
// allowedNamespacesAsString returns a string containing comma-separated list
// of allowed application namespaces
func (a *ArgoCDServer) allowedApplicationNamespacesAsString() string {
ns := a.Namespace
if len(a.ArgoCDServerOpts.ApplicationNamespaces) > 0 {
ns += ", "
ns += strings.Join(a.ArgoCDServerOpts.ApplicationNamespaces, ", ")
}
return ns
}

View file

@ -125,6 +125,7 @@ func (s *Server) Get(ctx context.Context, q *settingspkg.SettingsQuery) (*settin
set.UiBannerURL = argoCDSettings.UiBannerURL
set.UiBannerPermanent = argoCDSettings.UiBannerPermanent
set.UiBannerPosition = argoCDSettings.UiBannerPosition
set.ControllerNamespace = s.mgr.GetNamespace()
}
if argoCDSettings.DexConfig != "" {
var cfg settingspkg.DexConfig

View file

@ -39,6 +39,7 @@ message Settings {
string uiBannerPosition = 20;
string statusBadgeRootUrl = 21;
bool execEnabled = 22;
string controllerNamespace = 23;
}
message GoogleAnalyticsConfig {

View file

@ -1,5 +1,5 @@
controller: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
api-server: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_BINARY_NAME=argocd-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} "
controller: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''}"
api-server: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_BINARY_NAME=argocd-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} "
dex: sh -c "test $ARGOCD_IN_CI = true && exit 0; ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/cmd gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:v2.30.0 serve /dex.yaml"
redis: sh -c "/usr/local/bin/redis-server --save "" --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}"
repo-server: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_BINARY_NAME=argocd-repo-server $COMMAND --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"

View file

@ -0,0 +1,102 @@
package e2e
import (
"context"
"testing"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
. "github.com/argoproj/gitops-engine/pkg/sync/common"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/types"
. "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/test/e2e/fixture"
. "github.com/argoproj/argo-cd/v2/test/e2e/fixture/app"
"github.com/argoproj/argo-cd/v2/util/errors"
)
func TestNSAutoSyncSelfHealDisabled(t *testing.T) {
Given(t).
SetTrackingMethod("annotation").
Path(guestbookPath).
SetAppNamespace(fixture.AppNamespace()).
// TODO: There is a bug with annotation tracking method that prevents
// controller from picking up changes in the cluster.
When().
// app should be auto-synced once created
CreateFromFile(func(app *Application) {
app.Spec.SyncPolicy = &SyncPolicy{Automated: &SyncPolicyAutomated{SelfHeal: false}}
}).
Then().
Expect(SyncStatusIs(SyncStatusCodeSynced)).
// app should be auto-synced if git change detected
When().
PatchFile("guestbook-ui-deployment.yaml", `[{"op": "replace", "path": "/spec/revisionHistoryLimit", "value": 1}]`).
Refresh(RefreshTypeNormal).
Then().
Expect(SyncStatusIs(SyncStatusCodeSynced)).
// app should not be auto-synced if k8s change detected
When().
And(func() {
errors.FailOnErr(fixture.KubeClientset.AppsV1().Deployments(fixture.DeploymentNamespace()).Patch(context.Background(),
"guestbook-ui", types.MergePatchType, []byte(`{"spec": {"revisionHistoryLimit": 0}}`), v1.PatchOptions{}))
}).
Then().
Expect(SyncStatusIs(SyncStatusCodeOutOfSync))
}
func TestNSAutoSyncSelfHealEnabled(t *testing.T) {
Given(t).
SetTrackingMethod("annotation").
Path(guestbookPath).
SetAppNamespace(fixture.AppNamespace()).
When().
// app should be auto-synced once created
CreateFromFile(func(app *Application) {
app.Spec.SyncPolicy = &SyncPolicy{
Automated: &SyncPolicyAutomated{SelfHeal: true},
Retry: &RetryStrategy{Limit: 0},
}
}).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
When().
// app should be auto-synced once k8s change detected
And(func() {
errors.FailOnErr(fixture.KubeClientset.AppsV1().Deployments(fixture.DeploymentNamespace()).Patch(context.Background(),
"guestbook-ui", types.MergePatchType, []byte(`{"spec": {"revisionHistoryLimit": 0}}`), v1.PatchOptions{}))
}).
Refresh(RefreshTypeNormal).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
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"}]`).
Refresh(RefreshTypeNormal).
Then().
Expect(OperationPhaseIs(OperationFailed)).
When().
// Trigger refresh again to make sure controller notices previously failed sync attempt before expectation timeout expires
Refresh(RefreshTypeNormal).
Then().
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
Expect(Condition(ApplicationConditionSyncError, "Failed sync attempt")).
When().
// SyncError condition should be removed after successful sync
PatchFile("guestbook-ui-deployment.yaml", `[{"op": "replace", "path": "/spec/revisionHistoryLimit", "value": 1}]`).
Refresh(RefreshTypeNormal).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
When().
// Trigger refresh twice to make sure controller notices successful attempt and removes condition
Refresh(RefreshTypeNormal).
Then().
Expect(SyncStatusIs(SyncStatusCodeSynced)).
And(func(app *Application) {
assert.Len(t, app.Status.Conditions, 0)
})
}

View file

@ -38,6 +38,7 @@ func TestAutoSyncSelfHealDisabled(t *testing.T) {
errors.FailOnErr(fixture.KubeClientset.AppsV1().Deployments(fixture.DeploymentNamespace()).Patch(context.Background(),
"guestbook-ui", types.MergePatchType, []byte(`{"spec": {"revisionHistoryLimit": 0}}`), v1.PatchOptions{}))
}).
Refresh(RefreshTypeNormal).
Then().
Expect(SyncStatusIs(SyncStatusCodeOutOfSync))
}

File diff suppressed because it is too large Load diff

View file

@ -273,7 +273,6 @@ func TestSyncToSignedCommitKeyWithKnownKey(t *testing.T) {
func TestAppCreation(t *testing.T) {
ctx := Given(t)
ctx.
Path(guestbookPath).
When().
@ -326,7 +325,7 @@ func TestAppCreationWithoutForceUpdate(t *testing.T) {
Then().
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
And(func(app *Application) {
assert.Equal(t, Name(), app.Name)
assert.Equal(t, ctx.AppName(), app.Name)
assert.Equal(t, RepoURL(RepoURLTypeFile), app.Spec.Source.RepoURL)
assert.Equal(t, guestbookPath, app.Spec.Source.Path)
assert.Equal(t, DeploymentNamespace(), app.Spec.Destination.Namespace)
@ -512,21 +511,20 @@ func TestAppRollbackSuccessful(t *testing.T) {
Source: app.Spec.Source,
}}
patch, _, err := diff.CreateTwoWayMergePatch(app, appWithHistory, &Application{})
assert.NoError(t, err)
require.NoError(t, err)
app, err = AppClientset.ArgoprojV1alpha1().Applications(ArgoCDNamespace).Patch(context.Background(), app.Name, types.MergePatchType, patch, metav1.PatchOptions{})
assert.NoError(t, err)
require.NoError(t, err)
// sync app and make sure it reaches InSync state
_, err = RunCli("app", "rollback", app.Name, "1")
assert.NoError(t, err)
require.NoError(t, err)
}).
Expect(Event(EventReasonOperationStarted, "rollback")).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
And(func(app *Application) {
assert.Equal(t, SyncStatusCodeSynced, app.Status.Sync.Status)
assert.NotNil(t, app.Status.OperationState.SyncResult)
require.NotNil(t, app.Status.OperationState.SyncResult)
assert.Equal(t, 2, len(app.Status.OperationState.SyncResult.Resources))
assert.Equal(t, OperationSucceeded, app.Status.OperationState.Phase)
assert.Equal(t, 3, len(app.Status.History))
@ -1179,7 +1177,9 @@ func TestPermissions(t *testing.T) {
And(func(app *Application) {
closer, cdClient := ArgoCDClientset.NewApplicationClientOrDie()
defer io.Close(closer)
tree, err := cdClient.ResourceTree(context.Background(), &applicationpkg.ResourcesQuery{ApplicationName: &app.Name})
appName, appNs := argo.ParseAppQualifiedName(app.Name, "")
fmt.Printf("APP NAME: %s\n", appName)
tree, err := cdClient.ResourceTree(context.Background(), &applicationpkg.ResourcesQuery{ApplicationName: &appName, AppNamespace: &appNs})
require.NoError(t, err)
assert.Len(t, tree.Nodes, 0)
assert.Len(t, tree.OrphanedNodes, 0)
@ -1204,6 +1204,7 @@ func TestPermissions(t *testing.T) {
func TestPermissionWithScopedRepo(t *testing.T) {
projName := "argo-project"
fixture.EnsureCleanState(t)
projectFixture.
Given(t).
Name(projName).
@ -2012,7 +2013,8 @@ func TestAppLogs(t *testing.T) {
}
func TestAppWaitOperationInProgress(t *testing.T) {
Given(t).
ctx := Given(t)
ctx.
And(func() {
SetResourceOverrides(map[string]ResourceOverride{
"batch/Job": {
@ -2033,7 +2035,7 @@ func TestAppWaitOperationInProgress(t *testing.T) {
Expect(OperationPhaseIs(OperationRunning)).
When().
And(func() {
_, err := RunCli("app", "wait", Name(), "--suspended")
_, err := RunCli("app", "wait", ctx.AppName(), "--suspended")
errors.CheckError(err)
})
}

View file

@ -0,0 +1,105 @@
package e2e
import (
"context"
"testing"
. "github.com/argoproj/gitops-engine/pkg/sync/common"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
. "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
. "github.com/argoproj/argo-cd/v2/test/e2e/fixture"
. "github.com/argoproj/argo-cd/v2/test/e2e/fixture/app"
. "github.com/argoproj/argo-cd/v2/util/argo"
. "github.com/argoproj/argo-cd/v2/util/errors"
)
func TestAppCreationInOtherNamespace(t *testing.T) {
ctx := Given(t)
ctx.
Path(guestbookPath).
SetAppNamespace(ArgoCDAppNamespace).
When().
CreateApp().
Then().
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
And(func(app *Application) {
assert.Equal(t, ctx.AppName(), app.Name)
assert.Equal(t, AppNamespace(), app.Namespace)
assert.Equal(t, RepoURL(RepoURLTypeFile), app.Spec.Source.RepoURL)
assert.Equal(t, guestbookPath, app.Spec.Source.Path)
assert.Equal(t, DeploymentNamespace(), app.Spec.Destination.Namespace)
assert.Equal(t, KubernetesInternalAPIServerAddr, app.Spec.Destination.Server)
}).
Expect(NamespacedEvent(ctx.AppNamespace(), EventReasonResourceCreated, "create")).
And(func(_ *Application) {
// app should be listed
output, err := RunCli("app", "list")
assert.NoError(t, err)
assert.Contains(t, output, ctx.AppName())
}).
When().
// ensure that create is idempotent
CreateApp().
Then().
Given().
Revision("master").
When().
// ensure that update replaces spec and merge labels and annotations
And(func() {
FailOnErr(AppClientset.ArgoprojV1alpha1().Applications(AppNamespace()).Patch(context.Background(),
ctx.AppName(), types.MergePatchType, []byte(`{"metadata": {"labels": { "test": "label" }, "annotations": { "test": "annotation" }}}`), metav1.PatchOptions{}))
}).
CreateApp("--upsert").
Then().
And(func(app *Application) {
assert.Equal(t, "label", app.Labels["test"])
assert.Equal(t, "annotation", app.Annotations["test"])
assert.Equal(t, "master", app.Spec.Source.TargetRevision)
})
}
func TestForbiddenNamespace(t *testing.T) {
ctx := Given(t)
ctx.
Path(guestbookPath).
SetAppNamespace("forbidden").
When().
IgnoreErrors().
CreateApp().
Then().
Expect(DoesNotExist())
}
func TestDeletingNamespacedAppStuckInSync(t *testing.T) {
ctx := Given(t)
ctx.And(func() {
SetResourceOverrides(map[string]ResourceOverride{
"ConfigMap": {
HealthLua: `return { status = obj.annotations and obj.annotations['health'] or 'Progressing' }`,
},
})
}).
Async(true).
SetAppNamespace(ArgoCDAppNamespace).
Path("hook-custom-health").
When().
CreateApp().
Sync().
Then().
// stuck in running state
Expect(OperationPhaseIs(OperationRunning)).
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
When().
Delete(true).
Then().
// delete is ignored, still stuck in running state
Expect(OperationPhaseIs(OperationRunning)).
When().
TerminateOp().
Then().
// delete is successful
Expect(DoesNotExist())
}

View file

@ -19,7 +19,8 @@ import (
// make sure we can echo back the Git creds
func TestCustomToolWithGitCreds(t *testing.T) {
Given(t).
ctx := Given(t)
ctx.
// path does not matter, we ignore it
ConfigManagementPlugin(
ConfigManagementPlugin{
@ -43,7 +44,7 @@ func TestCustomToolWithGitCreds(t *testing.T) {
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy)).
And(func(app *Application) {
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.GitAskpass}")
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", ctx.AppName(), "-o", "jsonpath={.metadata.annotations.GitAskpass}")
assert.NoError(t, err)
assert.Equal(t, "argocd", output)
})
@ -51,7 +52,8 @@ func TestCustomToolWithGitCreds(t *testing.T) {
// make sure we can echo back the Git creds
func TestCustomToolWithGitCredsTemplate(t *testing.T) {
Given(t).
ctx := Given(t)
ctx.
// path does not matter, we ignore it
ConfigManagementPlugin(
ConfigManagementPlugin{
@ -77,17 +79,17 @@ func TestCustomToolWithGitCredsTemplate(t *testing.T) {
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy)).
And(func(app *Application) {
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.GitAskpass}")
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", ctx.AppName(), "-o", "jsonpath={.metadata.annotations.GitAskpass}")
assert.NoError(t, err)
assert.Equal(t, "argocd", output)
}).
And(func(app *Application) {
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.GitUsername}")
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", ctx.AppName(), "-o", "jsonpath={.metadata.annotations.GitUsername}")
assert.NoError(t, err)
assert.Empty(t, output)
}).
And(func(app *Application) {
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.GitPassword}")
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", ctx.AppName(), "-o", "jsonpath={.metadata.annotations.GitPassword}")
assert.NoError(t, err)
assert.Empty(t, output)
})
@ -95,7 +97,8 @@ func TestCustomToolWithGitCredsTemplate(t *testing.T) {
// make sure we can echo back the env
func TestCustomToolWithEnv(t *testing.T) {
Given(t).
ctx := Given(t)
ctx.
// path does not matter, we ignore it
ConfigManagementPlugin(
ConfigManagementPlugin{
@ -124,18 +127,18 @@ func TestCustomToolWithEnv(t *testing.T) {
time.Sleep(1 * time.Second)
}).
And(func(app *Application) {
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.Bar}")
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", ctx.AppName(), "-o", "jsonpath={.metadata.annotations.Bar}")
assert.NoError(t, err)
assert.Equal(t, "baz", output)
}).
And(func(app *Application) {
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.Foo}")
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", ctx.AppName(), "-o", "jsonpath={.metadata.annotations.Foo}")
assert.NoError(t, err)
assert.Equal(t, "bar", output)
}).
And(func(app *Application) {
expectedKubeVersion := GetVersions().ServerVersion.Format("%s.%s")
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.KubeVersion}")
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", ctx.AppName(), "-o", "jsonpath={.metadata.annotations.KubeVersion}")
assert.NoError(t, err)
assert.Equal(t, expectedKubeVersion, output)
}).
@ -144,7 +147,7 @@ func TestCustomToolWithEnv(t *testing.T) {
expectedApiVersionSlice := strings.Split(expectedApiVersion, ",")
sort.Strings(expectedApiVersionSlice)
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.KubeApiVersion}")
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", ctx.AppName(), "-o", "jsonpath={.metadata.annotations.KubeApiVersion}")
assert.NoError(t, err)
outputSlice := strings.Split(output, ",")
sort.Strings(outputSlice)
@ -155,7 +158,8 @@ func TestCustomToolWithEnv(t *testing.T) {
//make sure we can sync and diff with --local
func TestCustomToolSyncAndDiffLocal(t *testing.T) {
Given(t).
ctx := Given(t)
ctx.
// path does not matter, we ignore it
ConfigManagementPlugin(
ConfigManagementPlugin{
@ -169,7 +173,7 @@ func TestCustomToolSyncAndDiffLocal(t *testing.T) {
// does not matter what the path is
Path("guestbook").
When().
CreateApp("--config-management-plugin", Name()).
CreateApp("--config-management-plugin", ctx.AppName()).
Sync("--local", "testdata/guestbook").
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
@ -179,10 +183,10 @@ func TestCustomToolSyncAndDiffLocal(t *testing.T) {
time.Sleep(1 * time.Second)
}).
And(func(app *Application) {
FailOnErr(RunCli("app", "sync", app.Name, "--local", "testdata/guestbook"))
FailOnErr(RunCli("app", "sync", ctx.AppName(), "--local", "testdata/guestbook"))
}).
And(func(app *Application) {
FailOnErr(RunCli("app", "diff", app.Name, "--local", "testdata/guestbook"))
FailOnErr(RunCli("app", "diff", ctx.AppName(), "--local", "testdata/guestbook"))
})
}
@ -239,7 +243,8 @@ func TestCMPDiscoverWithFindGlob(t *testing.T) {
//Discover by Find command
func TestCMPDiscoverWithFindCommandWithEnv(t *testing.T) {
pluginName := "cmp-find-command"
Given(t).
ctx := Given(t)
ctx.
And(func() {
go startCMPServer("./testdata/cmp-find-command")
time.Sleep(1 * time.Second)
@ -257,13 +262,13 @@ func TestCMPDiscoverWithFindCommandWithEnv(t *testing.T) {
time.Sleep(1 * time.Second)
}).
And(func(app *Application) {
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.Bar}")
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", ctx.AppName(), "-o", "jsonpath={.metadata.annotations.Bar}")
assert.NoError(t, err)
assert.Equal(t, "baz", output)
}).
And(func(app *Application) {
expectedKubeVersion := GetVersions().ServerVersion.Format("%s.%s")
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.KubeVersion}")
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", ctx.AppName(), "-o", "jsonpath={.metadata.annotations.KubeVersion}")
assert.NoError(t, err)
assert.Equal(t, expectedKubeVersion, output)
}).
@ -272,7 +277,7 @@ func TestCMPDiscoverWithFindCommandWithEnv(t *testing.T) {
expectedApiVersionSlice := strings.Split(expectedApiVersion, ",")
sort.Strings(expectedApiVersionSlice)
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.KubeApiVersion}")
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", ctx.AppName(), "-o", "jsonpath={.metadata.annotations.KubeApiVersion}")
assert.NoError(t, err)
outputSlice := strings.Split(output, ",")
sort.Strings(outputSlice)

View file

@ -54,11 +54,11 @@ func TestDeploymentWithAnnotationTrackingMode(t *testing.T) {
When().
Then().
And(func(app *Application) {
out, err := RunCli("app", "manifests", app.Name)
out, err := RunCli("app", "manifests", ctx.AppName())
assert.NoError(t, err)
assert.Contains(t, out, fmt.Sprintf(`annotations:
argocd.argoproj.io/tracking-id: %s:apps/Deployment:%s/nginx-deployment
`, Name(), DeploymentNamespace()))
`, ctx.AppName(), DeploymentNamespace()))
})
}
@ -77,17 +77,18 @@ func TestDeploymentWithLabelTrackingMode(t *testing.T) {
When().
Then().
And(func(app *Application) {
out, err := RunCli("app", "manifests", app.Name)
out, err := RunCli("app", "manifests", ctx.AppName())
assert.NoError(t, err)
assert.Contains(t, out, fmt.Sprintf(`labels:
app: nginx
app.kubernetes.io/instance: %s
`, Name()))
`, ctx.AppName()))
})
}
func TestDeploymentWithoutTrackingMode(t *testing.T) {
Given(t).
ctx := Given(t)
ctx.
Path("deployment").
When().
CreateApp().
@ -99,11 +100,11 @@ func TestDeploymentWithoutTrackingMode(t *testing.T) {
When().
Then().
And(func(app *Application) {
out, err := RunCli("app", "manifests", app.Name)
out, err := RunCli("app", "manifests", ctx.AppName())
assert.NoError(t, err)
assert.Contains(t, out, fmt.Sprintf(`labels:
app: nginx
app.kubernetes.io/instance: %s
`, Name()))
`, ctx.AppName()))
})
}

View file

@ -74,11 +74,14 @@ func (a *Actions) CreateFromPartialFile(data string, flags ...string) *Actions {
args := append([]string{
"app", "create",
"-f", tmpFile.Name(),
"--name", a.context.name,
"--name", a.context.AppName(),
"--repo", fixture.RepoURL(a.context.repoURLType),
"--dest-server", a.context.destServer,
"--dest-namespace", fixture.DeploymentNamespace(),
}, flags...)
if a.context.appNamespace != "" {
args = append(args, "--app-namespace", a.context.appNamespace)
}
defer tmpFile.Close()
a.runCli(args...)
return a
@ -87,7 +90,8 @@ func (a *Actions) CreateFromFile(handler func(app *Application), flags ...string
a.context.t.Helper()
app := &Application{
ObjectMeta: v1.ObjectMeta{
Name: a.context.name,
Name: a.context.AppName(),
Namespace: a.context.AppNamespace(),
},
Spec: ApplicationSpec{
Project: a.context.project,
@ -157,7 +161,7 @@ func (a *Actions) CreateApp(args ...string) *Actions {
func (a *Actions) prepareCreateAppArgs(args []string) []string {
a.context.t.Helper()
args = append([]string{
"app", "create", a.context.name,
"app", "create", a.context.AppQualifiedName(),
"--repo", fixture.RepoURL(a.context.repoURLType),
}, args...)
@ -218,7 +222,7 @@ func (a *Actions) DeclarativeWithCustomRepo(filename string, repoURL string) *Ac
values := map[string]interface{}{
"ArgoCDNamespace": fixture.ArgoCDNamespace,
"DeploymentNamespace": fixture.DeploymentNamespace(),
"Name": a.context.name,
"Name": a.context.AppName(),
"Path": a.context.path,
"Project": a.context.project,
"RepoURL": repoURL,
@ -230,13 +234,13 @@ func (a *Actions) DeclarativeWithCustomRepo(filename string, repoURL string) *Ac
func (a *Actions) PatchApp(patch string) *Actions {
a.context.t.Helper()
a.runCli("app", "patch", a.context.name, "--patch", patch)
a.runCli("app", "patch", a.context.AppQualifiedName(), "--patch", patch)
return a
}
func (a *Actions) AppSet(flags ...string) *Actions {
a.context.t.Helper()
args := []string{"app", "set", a.context.name}
args := []string{"app", "set", a.context.AppQualifiedName()}
args = append(args, flags...)
a.runCli(args...)
return a
@ -244,7 +248,7 @@ func (a *Actions) AppSet(flags ...string) *Actions {
func (a *Actions) AppUnSet(flags ...string) *Actions {
a.context.t.Helper()
args := []string{"app", "unset", a.context.name}
args := []string{"app", "unset", a.context.AppQualifiedName()}
args = append(args, flags...)
a.runCli(args...)
return a
@ -254,7 +258,7 @@ func (a *Actions) Sync(args ...string) *Actions {
a.context.t.Helper()
args = append([]string{"app", "sync"}, args...)
if a.context.name != "" {
args = append(args, a.context.name)
args = append(args, a.context.AppQualifiedName())
}
args = append(args, "--timeout", fmt.Sprintf("%v", a.context.timeout))
@ -291,7 +295,7 @@ func (a *Actions) Sync(args ...string) *Actions {
func (a *Actions) TerminateOp() *Actions {
a.context.t.Helper()
a.runCli("app", "terminate-op", a.context.name)
a.runCli("app", "terminate-op", a.context.AppQualifiedName())
return a
}
@ -302,14 +306,14 @@ func (a *Actions) Refresh(refreshType RefreshType) *Actions {
RefreshTypeHard: "--hard-refresh",
}[refreshType]
a.runCli("app", "get", a.context.name, flag)
a.runCli("app", "get", a.context.AppQualifiedName(), flag)
return a
}
func (a *Actions) Delete(cascade bool) *Actions {
a.context.t.Helper()
a.runCli("app", "delete", a.context.name, fmt.Sprintf("--cascade=%v", cascade), "--yes")
a.runCli("app", "delete", a.context.AppQualifiedName(), fmt.Sprintf("--cascade=%v", cascade), "--yes")
return a
}

View file

@ -69,7 +69,7 @@ func (c *Consequences) app() *Application {
}
func (c *Consequences) get() (*Application, error) {
return fixture.AppClientset.ArgoprojV1alpha1().Applications(fixture.ArgoCDNamespace).Get(context.Background(), c.context.name, v1.GetOptions{})
return fixture.AppClientset.ArgoprojV1alpha1().Applications(c.context.AppNamespace()).Get(context.Background(), c.context.AppName(), v1.GetOptions{})
}
func (c *Consequences) resource(kind, name, namespace string) ResourceStatus {

View file

@ -9,6 +9,7 @@ import (
"github.com/argoproj/argo-cd/v2/test/e2e/fixture/certs"
"github.com/argoproj/argo-cd/v2/test/e2e/fixture/gpgkeys"
"github.com/argoproj/argo-cd/v2/test/e2e/fixture/repos"
"github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/env"
"github.com/argoproj/argo-cd/v2/util/settings"
)
@ -22,6 +23,7 @@ type Context struct {
// seconds
timeout int
name string
appNamespace string
destServer string
destName string
env string
@ -40,6 +42,11 @@ type Context struct {
replace bool
helmPassCredentials bool
helmSkipCrds bool
trackingMethod v1alpha1.TrackingMethod
}
type ContextArgs struct {
AppNamespace string
}
func Given(t *testing.T) *Context {
@ -47,11 +54,52 @@ func Given(t *testing.T) *Context {
return GivenWithSameState(t)
}
func GivenWithNamespace(t *testing.T, namespace string) *Context {
ctx := Given(t)
ctx.appNamespace = namespace
return ctx
}
func GivenWithSameState(t *testing.T) *Context {
// ARGOCE_E2E_DEFAULT_TIMEOUT can be used to override the default timeout
// for any context.
timeout := env.ParseNumFromEnv("ARGOCD_E2E_DEFAULT_TIMEOUT", 10, 0, 180)
return &Context{t: t, destServer: v1alpha1.KubernetesInternalAPIServerAddr, repoURLType: fixture.RepoURLTypeFile, name: fixture.Name(), timeout: timeout, project: "default", prune: true}
return &Context{
t: t,
destServer: v1alpha1.KubernetesInternalAPIServerAddr,
repoURLType: fixture.RepoURLTypeFile,
name: fixture.Name(),
timeout: timeout,
project: "default",
prune: true,
trackingMethod: argo.TrackingMethodLabel,
}
}
func (c *Context) AppName() string {
return c.name
}
func (c *Context) AppQualifiedName() string {
if c.appNamespace != "" {
return c.appNamespace + "/" + c.AppName()
} else {
return c.AppName()
}
}
func (c *Context) AppNamespace() string {
if c.appNamespace != "" {
return c.appNamespace
} else {
return fixture.TestNamespace()
}
}
func (c *Context) SetAppNamespace(namespace string) *Context {
c.appNamespace = namespace
//fixture.SetParamInSettingConfigMap("application.resourceTrackingMethod", "annotation")
return c
}
func (c *Context) GPGPublicKeyAdded() *Context {
@ -313,3 +361,7 @@ func (c *Context) SetTrackingMethod(trackingMethod string) *Context {
fixture.SetTrackingMethod(trackingMethod)
return c
}
func (c *Context) GetTrackingMethod() v1alpha1.TrackingMethod {
return c.trackingMethod
}

View file

@ -218,12 +218,12 @@ func pods() (*v1.PodList, error) {
return pods, err
}
func Event(reason string, message string) Expectation {
func event(namespace string, reason string, message string) Expectation {
return func(c *Consequences) (state, string) {
list, err := fixture.KubeClientset.CoreV1().Events(fixture.ArgoCDNamespace).List(context.Background(), metav1.ListOptions{
list, err := fixture.KubeClientset.CoreV1().Events(namespace).List(context.Background(), metav1.ListOptions{
FieldSelector: fields.SelectorFromSet(map[string]string{
"involvedObject.name": c.context.name,
"involvedObject.namespace": fixture.ArgoCDNamespace,
"involvedObject.name": c.context.AppName(),
"involvedObject.namespace": namespace,
}).String(),
})
if err != nil {
@ -240,6 +240,14 @@ func Event(reason string, message string) Expectation {
}
}
func Event(reason string, message string) Expectation {
return event(fixture.ArgoCDNamespace, reason, message)
}
func NamespacedEvent(namespace string, reason string, message string) Expectation {
return event(namespace, reason, message)
}
// asserts that the last command was successful
func Success(message string) Expectation {
return func(c *Consequences) (state, string) {

View file

@ -43,6 +43,7 @@ const (
DefaultTestUserPassword = "password"
testingLabel = "e2e.argoproj.io"
ArgoCDNamespace = "argocd-e2e"
ArgoCDAppNamespace = "argocd-e2e-external"
// ensure all repos are in one directory tree, so we can easily clean them up
TmpDir = "/tmp/argo-e2e"
@ -112,6 +113,10 @@ func TestNamespace() string {
return GetEnvWithDefault("ARGOCD_E2E_NAMESPACE", ArgoCDNamespace)
}
func AppNamespace() string {
return GetEnvWithDefault("ARGOCD_E2E_APP_NAMESPACE", ArgoCDAppNamespace)
}
// getKubeConfig creates new kubernetes client config using specified config path and config overrides variables
func getKubeConfig(configPath string, overrides clientcmd.ConfigOverrides) *rest.Config {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
@ -511,6 +516,7 @@ func EnsureCleanState(t *testing.T) {
// delete resources
// kubectl delete apps --all
CheckError(AppClientset.ArgoprojV1alpha1().Applications(TestNamespace()).DeleteCollection(context.Background(), v1.DeleteOptions{PropagationPolicy: &policy}, v1.ListOptions{}))
CheckError(AppClientset.ArgoprojV1alpha1().Applications(AppNamespace()).DeleteCollection(context.Background(), v1.DeleteOptions{PropagationPolicy: &policy}, v1.ListOptions{}))
// kubectl delete appprojects --field-selector metadata.name!=default
CheckError(AppClientset.ArgoprojV1alpha1().AppProjects(TestNamespace()).DeleteCollection(context.Background(),
v1.DeleteOptions{PropagationPolicy: &policy}, v1.ListOptions{FieldSelector: "metadata.name!=default"}))
@ -556,6 +562,7 @@ func EnsureCleanState(t *testing.T) {
SourceRepos: []string{"*"},
Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "*"}},
ClusterResourceWhitelist: []v1.GroupKind{{Group: "*", Kind: "*"}},
SourceNamespaces: []string{AppNamespace()},
})
// Create separate project for testing gpg signature verification
@ -566,6 +573,7 @@ func EnsureCleanState(t *testing.T) {
Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "*"}},
ClusterResourceWhitelist: []v1.GroupKind{{Group: "*", Kind: "*"}},
SignatureKeys: []v1alpha1.SignatureKey{{KeyID: GpgGoodKeyID}},
SourceNamespaces: []string{AppNamespace()},
})
// Recreate temp dir

View file

@ -2,6 +2,7 @@ package project
import (
"context"
"strings"
"github.com/stretchr/testify/require"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -74,6 +75,9 @@ func (a *Actions) prepareCreateArgs(args []string) []string {
args = append(args, "--dest", a.context.destination)
}
if len(a.context.sourceNamespaces) > 0 {
args = append(args, "--source-namespaces", strings.Join(a.context.sourceNamespaces, ","))
}
return args
}

View file

@ -12,10 +12,11 @@ import (
type Context struct {
t *testing.T
// seconds
timeout int
name string
destination string
repos []string
timeout int
name string
destination string
repos []string
sourceNamespaces []string
}
func Given(t *testing.T) *Context {
@ -49,6 +50,11 @@ func (c *Context) SourceRepositories(repos []string) *Context {
return c
}
func (c *Context) SourceNamespaces(namespaces []string) *Context {
c.sourceNamespaces = namespaces
return c
}
func (c *Context) And(block func()) *Context {
block()
return c

View file

@ -1,6 +1,8 @@
package repos
import (
"log"
"github.com/argoproj/argo-cd/v2/test/e2e/fixture"
)
@ -81,4 +83,7 @@ func (a *Actions) Then() *Consequences {
func (a *Actions) runCli(args ...string) {
a.context.t.Helper()
a.lastOutput, a.lastError = fixture.RunCli(args...)
if !a.ignoreErrors && a.lastError != nil {
log.Fatal(a.lastOutput)
}
}

View file

@ -53,6 +53,7 @@ func TestCreateRepositoryNonAdminUserPermissionDenied(t *testing.T) {
When().
Path(path).
Project("argo-project").
IgnoreErrors().
Create().
Then().
AndCLIOutput(func(output string, err error) {
@ -79,6 +80,7 @@ func TestCreateRepositoryNonAdminUserWithWrongProject(t *testing.T) {
When().
Path(path).
Project("argo-project").
IgnoreErrors().
Create().
Then().
AndCLIOutput(func(output string, err error) {
@ -165,6 +167,7 @@ func TestDeleteRepositoryRbacDenied(t *testing.T) {
assert.Equal(t, r.Project, "argo-project")
}).
When().
IgnoreErrors().
Delete().
Then().
AndCLIOutput(func(output string, err error) {

View file

@ -3,6 +3,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: clusterdummies.argoproj.io
labels:
e2e.argoproj.io: "true"
spec:
conversion:
strategy: None
@ -50,4 +52,4 @@ apiVersion: argoproj.io/v1alpha1
kind: ClusterDummy
metadata:
name: cluster-dummy-crd-instance
namespace: kube-system
namespace: kube-system

View file

@ -3,6 +3,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: dummies.argoproj.io
labels:
e2e.argoproj.io: "true"
spec:
conversion:
strategy: None

View file

@ -2,6 +2,8 @@ apiVersion: apps/v1
kind: Deployment
metadata:
name: guestbook-ui
labels:
test: "true"
spec:
replicas: 0
revisionHistoryLimit: 3

View file

@ -2,7 +2,6 @@ apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: test-self-managed-apps
namespace: argocd-e2e
spec:
project: default

View file

@ -202,6 +202,15 @@ export const ApplicationCreatePanel = (props: {
component={Text}
/>
</div>
<div className='argo-form-row'>
<FormField
formApi={api}
label='Application Namespace'
qeId='application-create-field-app-namespace'
field='metadata.namespace'
component={Text}
/>
</div>
<div className='argo-form-row'>
<FormField
formApi={api}

View file

@ -76,6 +76,7 @@ export const ApplicationDeploymentHistory = ({
<React.Fragment>
<RevisionMetadataRows
applicationName={app.metadata.name}
applicationNamespace={app.metadata.namespace}
source={{...recentDeployments[index].source, targetRevision: recentDeployments[index].revision}}
/>
<DataLoader

View file

@ -4,7 +4,7 @@ import {Timestamp} from '../../../shared/components/timestamp';
import {ApplicationSource, RevisionMetadata} from '../../../shared/models';
import {services} from '../../../shared/services';
export const RevisionMetadataRows = (props: {applicationName: string; source: ApplicationSource}) => {
export const RevisionMetadataRows = (props: {applicationName: string; applicationNamespace: string; source: ApplicationSource}) => {
if (props.source.chart) {
return (
<div>
@ -20,7 +20,7 @@ export const RevisionMetadataRows = (props: {applicationName: string; source: Ap
);
}
return (
<DataLoader input={props} load={input => services.applications.revisionMetadata(input.applicationName, input.source.targetRevision)}>
<DataLoader input={props} load={input => services.applications.revisionMetadata(input.applicationName, input.applicationNamespace, input.source.targetRevision)}>
{(m: RevisionMetadata) => (
<div>
<div className='row'>

View file

@ -62,16 +62,22 @@ export const SelectNode = (fullName: string, containerIndex = 0, tab: string = n
appContext.navigation.goto('.', {node, tab}, {replace: true});
};
export class ApplicationDetails extends React.Component<RouteComponentProps<{name: string}>, ApplicationDetailsState> {
export class ApplicationDetails extends React.Component<RouteComponentProps<{appnamespace: string; name: string}>, ApplicationDetailsState> {
public static contextTypes = {
apis: PropTypes.object
};
private appChanged = new BehaviorSubject<appModels.Application>(null);
private appNamespace: string;
constructor(props: RouteComponentProps<{name: string}>) {
constructor(props: RouteComponentProps<{appnamespace: string; name: string}>) {
super(props);
this.state = {page: 0, groupedResources: [], slidingPanelPage: 0, filteredGraph: [], truncateNameOnRight: false, collapsedNodes: []};
if (typeof this.props.match.params.appnamespace === 'undefined') {
this.appNamespace = '';
} else {
this.appNamespace = this.props.match.params.appnamespace;
}
}
private get showOperationState() {
@ -145,7 +151,7 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{nam
loadingRenderer={() => <Page title='Application Details'>Loading...</Page>}
input={this.props.match.params.name}
load={name =>
combineLatest([this.loadAppInfo(name), services.viewPreferences.getPreferences(), q]).pipe(
combineLatest([this.loadAppInfo(name, this.appNamespace), services.viewPreferences.getPreferences(), q]).pipe(
map(items => {
const pref = items[1].appDetails;
const params = items[2];
@ -432,7 +438,9 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{nam
const resourceRow: any = {...resource, group: resource.group || ''};
const liveState =
typeof resource.group !== 'undefined' &&
(await services.applications.getResource(application.metadata.name, resource).catch(() => null));
(await services.applications
.getResource(application.metadata.name, application.metadata.namespace, resource)
.catch(() => null));
if (liveState?.metadata?.annotations?.[models.AnnotationHookKey]) {
resourceRow.syncOrder = liveState?.metadata.annotations[models.AnnotationHookKey];
if (liveState?.metadata?.annotations?.[models.AnnotationSyncWaveKey]) {
@ -543,7 +551,10 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{nam
</SlidingPanel>
<SlidingPanel isShown={!!this.state.revision} isMiddle={true} onClose={() => this.setState({revision: null})}>
{this.state.revision && (
<DataLoader load={() => services.applications.revisionMetadata(application.metadata.name, this.state.revision)}>
<DataLoader
load={() =>
services.applications.revisionMetadata(application.metadata.name, application.metadata.namespace, this.state.revision)
}>
{metadata => (
<div className='white-box' style={{marginTop: '1.5em'}}>
<div className='white-box__details'>
@ -646,7 +657,7 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{nam
items={[
{
title: 'Hard Refresh',
action: () => !refreshing && services.applications.get(app.metadata.name, 'hard')
action: () => !refreshing && services.applications.get(app.metadata.name, app.metadata.namespace, 'hard')
}
]}
anchor={() => <i className='fa fa-caret-down' />}
@ -656,7 +667,7 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{nam
disabled: !!refreshing,
action: () => {
if (!refreshing) {
services.applications.get(app.metadata.name, 'normal');
services.applications.get(app.metadata.name, app.metadata.namespace, 'normal');
AppUtils.setAppRefreshing(app);
this.appChanged.next(app);
}
@ -688,8 +699,8 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{nam
return false;
}
private loadAppInfo(name: string): Observable<{application: appModels.Application; tree: appModels.ApplicationTree}> {
return from(services.applications.get(name))
private loadAppInfo(name: string, appNamespace: string): Observable<{application: appModels.Application; tree: appModels.ApplicationTree}> {
return from(services.applications.get(name, appNamespace))
.pipe(
mergeMap(app => {
const fallbackTree = {
@ -703,7 +714,7 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{nam
this.appChanged.pipe(filter(item => !!item)),
AppUtils.handlePageVisibility(() =>
services.applications
.watch({name})
.watch({name, appNamespace})
.pipe(
map(watchEvent => {
if (watchEvent.type === 'DELETED') {
@ -718,10 +729,10 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{nam
),
merge(
from([fallbackTree]),
services.applications.resourceTree(name).catch(() => fallbackTree),
services.applications.resourceTree(name, appNamespace).catch(() => fallbackTree),
AppUtils.handlePageVisibility(() =>
services.applications
.watchResourceTree(name)
.watchResourceTree(name, appNamespace)
.pipe(repeat())
.pipe(retryWhen(errors => errors.pipe(delay(500))))
)
@ -739,7 +750,7 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{nam
}
private async updateApp(app: appModels.Application, query: {validate?: boolean}) {
const latestApp = await services.applications.get(app.metadata.name);
const latestApp = await services.applications.get(app.metadata.name, app.metadata.namespace);
latestApp.metadata.labels = app.metadata.labels;
latestApp.metadata.annotations = app.metadata.annotations;
latestApp.spec = app.spec;
@ -815,8 +826,8 @@ Are you sure you want to disable auto-sync and rollback application '${this.prop
update.spec.syncPolicy = {automated: null};
await services.applications.update(update);
}
await services.applications.rollback(this.props.match.params.name, revisionHistory.id);
this.appChanged.next(await services.applications.get(this.props.match.params.name));
await services.applications.rollback(this.props.match.params.name, this.appNamespace, revisionHistory.id);
this.appChanged.next(await services.applications.get(this.props.match.params.name, this.appNamespace));
this.setRollbackPanelVisible(-1);
}
} catch (e) {
@ -832,6 +843,6 @@ Are you sure you want to disable auto-sync and rollback application '${this.prop
}
private async deleteApplication() {
await AppUtils.deleteApplication(this.props.match.params.name, this.appContext.apis);
await AppUtils.deleteApplication(this.props.match.params.name, this.appNamespace, this.appContext.apis);
}
}

View file

@ -7,7 +7,7 @@ import {Context} from '../../../shared/context';
import {PodsLogsViewer} from '../pod-logs-viewer/pod-logs-viewer';
import './application-fullscreen-logs.scss';
export const ApplicationFullscreenLogs = (props: RouteComponentProps<{name: string; container: string; namespace: string}>) => {
export const ApplicationFullscreenLogs = (props: RouteComponentProps<{name: string; appnamespace: string; container: string; namespace: string}>) => {
const appContext = React.useContext(Context);
return (
<Query>
@ -25,6 +25,7 @@ export const ApplicationFullscreenLogs = (props: RouteComponentProps<{name: stri
<h4 style={{fontSize: '18px', textAlign: 'center'}}>{title}</h4>
<PodsLogsViewer
applicationName={props.match.params.name}
applicationNamespace={props.match.params.appnamespace}
containerName={props.match.params.container}
namespace={props.match.params.namespace}
group={group}

View file

@ -4,10 +4,14 @@ import * as React from 'react';
import {DataLoader, EventsList} from '../../../shared/components';
import {services} from '../../../shared/services';
export const ApplicationResourceEvents = (props: {applicationName: string; resource?: {namespace: string; name: string; uid: string}}) => (
export const ApplicationResourceEvents = (props: {applicationName: string; applicationNamespace: string; resource?: {namespace: string; name: string; uid: string}}) => (
<div className='application-resource-events'>
<DataLoader
load={() => (props.resource ? services.applications.resourceEvents(props.applicationName, props.resource) : services.applications.events(props.applicationName))}
load={() =>
props.resource
? services.applications.resourceEvents(props.applicationName, props.applicationNamespace, props.resource)
: services.applications.events(props.applicationName, props.applicationNamespace)
}
loadingRenderer={() => <MockupList height={50} marginTop={10} />}>
{events => <EventsList events={events} />}
</DataLoader>

View file

@ -574,7 +574,7 @@ function renderPodGroup(props: ApplicationResourceTreeProps, id: string, node: R
</React.Fragment>
),
action: () => {
deletePodAction(pod, props.appContext, props.app.metadata.name);
deletePodAction(pod, props.appContext, props.app.metadata.name, props.app.metadata.namespace);
}
}
]}

View file

@ -92,7 +92,12 @@ export const ApplicationStatusPanel = ({application, showOperation, showConditio
</div>
<div className='application-status-panel__item-name'>
{application.status && application.status.sync && application.status.sync.revision && (
<RevisionMetadataPanel appName={application.metadata.name} type={application.spec.source.chart && 'helm'} revision={application.status.sync.revision} />
<RevisionMetadataPanel
appName={application.metadata.name}
appNamespace={application.metadata.namespace}
type={application.spec.source.chart && 'helm'}
revision={application.status.sync.revision}
/>
)}
</div>
</React.Fragment>
@ -127,6 +132,7 @@ export const ApplicationStatusPanel = ({application, showOperation, showConditio
{(appOperationState.syncResult && appOperationState.syncResult.revision && (
<RevisionMetadataPanel
appName={application.metadata.name}
appNamespace={application.metadata.namespace}
type={application.spec.source.chart && 'helm'}
revision={appOperationState.syncResult.revision}
/>
@ -158,9 +164,9 @@ export const ApplicationStatusPanel = ({application, showOperation, showConditio
)}
<DataLoader
noLoaderOnInputChange={true}
input={application.metadata.name}
load={async name => {
return await services.applications.getApplicationSyncWindowState(name);
input={application}
load={async app => {
return await services.applications.getApplicationSyncWindowState(app.metadata.name, app.metadata.namespace);
}}>
{(data: models.ApplicationSyncWindowState) => (
<React.Fragment>

View file

@ -3,12 +3,12 @@ import * as React from 'react';
import {Timestamp} from '../../../shared/components/timestamp';
import {services} from '../../../shared/services';
export const RevisionMetadataPanel = (props: {appName: string; type: string; revision: string}) => {
export const RevisionMetadataPanel = (props: {appName: string; appNamespace: string; type: string; revision: string}) => {
if (props.type === 'helm') {
return <React.Fragment />;
}
return (
<DataLoader load={() => services.applications.revisionMetadata(props.appName, props.revision)} errorRenderer={() => <div />}>
<DataLoader load={() => services.applications.revisionMetadata(props.appName, props.appNamespace, props.revision)} errorRenderer={() => <div />}>
{m => (
<Tooltip
popperOptions={{

View file

@ -96,6 +96,7 @@ export const ApplicationSyncPanel = ({application, selectedResource, hide}: {app
try {
await services.applications.sync(
application.metadata.name,
application.metadata.namespace,
params.revision,
syncFlags.Prune || false,
syncFlags.DryRun || false,

View file

@ -8,6 +8,8 @@ export const ApplicationsContainer = (props: RouteComponentProps<any>) => (
<Switch>
<Route exact={true} path={`${props.match.path}`} component={ApplicationsList} />
<Route exact={true} path={`${props.match.path}/:name`} component={ApplicationDetails} />
<Route exact={true} path={`${props.match.path}/:appnamespace/:name`} component={ApplicationDetails} />
<Route exact={true} path={`${props.match.path}/:name/:namespace/:container/logs`} component={ApplicationFullscreenLogs} />
<Route exact={true} path={`${props.match.path}/:appnamespace/:name/:namespace/:container/logs`} component={ApplicationFullscreenLogs} />
</Switch>
);

View file

@ -27,6 +27,7 @@ const EVENTS_BUFFER_TIMEOUT = 500;
const WATCH_RETRY_TIMEOUT = 500;
const APP_FIELDS = [
'metadata.name',
'metadata.namespace',
'metadata.annotations',
'metadata.labels',
'metadata.creationTimestamp',
@ -43,8 +44,8 @@ const APP_FIELDS = [
const APP_LIST_FIELDS = ['metadata.resourceVersion', ...APP_FIELDS.map(field => `items.${field}`)];
const APP_WATCH_FIELDS = ['result.type', ...APP_FIELDS.map(field => `result.application.${field}`)];
function loadApplications(projects: string[]): Observable<models.Application[]> {
return from(services.applications.list(projects, {fields: APP_LIST_FIELDS})).pipe(
function loadApplications(projects: string[], appNamespace: string): Observable<models.Application[]> {
return from(services.applications.list(projects, {appNamespace, fields: APP_LIST_FIELDS})).pipe(
mergeMap(applicationsList => {
const applications = applicationsList.items;
return merge(
@ -58,7 +59,7 @@ function loadApplications(projects: string[]): Observable<models.Application[]>
.pipe(
map(appChanges => {
appChanges.forEach(appChange => {
const index = applications.findIndex(item => item.metadata.name === appChange.application.metadata.name);
const index = applications.findIndex(item => AppUtils.appInstanceName(item) === AppUtils.appInstanceName(appChange.application));
switch (appChange.type) {
case 'DELETED':
if (index > -1) {
@ -160,7 +161,9 @@ function filterApps(applications: models.Application[], pref: AppsListPreference
const filterResults = getFilterResults(applications, pref);
return {
filterResults,
filteredApps: filterResults.filter(app => (search === '' || app.metadata.name.includes(search)) && Object.values(app.filterResult).every(val => val))
filteredApps: filterResults.filter(
app => (search === '' || app.metadata.name.includes(search) || app.metadata.namespace.includes(search)) && Object.values(app.filterResult).every(val => val)
)
};
}
@ -250,7 +253,7 @@ const SearchBar = (props: {content: string; ctx: ContextApis; apps: models.Appli
}}
onChange={e => ctx.navigation.goto('.', {search: e.target.value}, {replace: true})}
value={content || ''}
items={apps.map(app => app.metadata.name)}
items={apps.map(app => app.metadata.namespace + '/' + app.metadata.name)}
/>
);
};
@ -361,7 +364,7 @@ export const ApplicationsList = (props: RouteComponentProps<{}>) => {
<DataLoader
input={pref.projectsFilter?.join(',')}
ref={loaderRef}
load={() => AppUtils.handlePageVisibility(() => loadApplications(pref.projectsFilter))}
load={() => AppUtils.handlePageVisibility(() => loadApplications(pref.projectsFilter, query.get('appNamespace')))}
loadingRenderer={() => (
<div className='argo-container'>
<MockupList height={100} marginTop={30} />
@ -489,16 +492,24 @@ export const ApplicationsList = (props: RouteComponentProps<{}>) => {
(pref.view === 'tiles' && (
<ApplicationTiles
applications={data}
syncApplication={appName => ctx.navigation.goto('.', {syncApp: appName}, {replace: true})}
syncApplication={(appName, appNamespace) =>
ctx.navigation.goto('.', {syncApp: appName, appNamespace}, {replace: true})
}
refreshApplication={refreshApp}
deleteApplication={appName => AppUtils.deleteApplication(appName, ctx)}
deleteApplication={(appName, appNamespace) =>
AppUtils.deleteApplication(appName, appNamespace, ctx)
}
/>
)) || (
<ApplicationsTable
applications={data}
syncApplication={appName => ctx.navigation.goto('.', {syncApp: appName}, {replace: true})}
syncApplication={(appName, appNamespace) =>
ctx.navigation.goto('.', {syncApp: appName, appNamespace}, {replace: true})
}
refreshApplication={refreshApp}
deleteApplication={appName => AppUtils.deleteApplication(appName, ctx)}
deleteApplication={(appName, appNamespace) =>
AppUtils.deleteApplication(appName, appNamespace, ctx)
}
/>
)
}
@ -526,7 +537,8 @@ export const ApplicationsList = (props: RouteComponentProps<{}>) => {
q.pipe(
mergeMap(params => {
const syncApp = params.get('syncApp');
return (syncApp && from(services.applications.get(syncApp))) || from([null]);
const appNamespace = params.get('appNamespace');
return (syncApp && from(services.applications.get(syncApp, appNamespace))) || from([null]);
})
)
}>

View file

@ -14,9 +14,9 @@ require('./applications-table.scss');
export const ApplicationsTable = (props: {
applications: models.Application[];
syncApplication: (appName: string) => any;
refreshApplication: (appName: string) => any;
deleteApplication: (appName: string) => any;
syncApplication: (appName: string, appNamespace: string) => any;
refreshApplication: (appName: string, appNamespace: string) => any;
deleteApplication: (appName: string, appNamespace: string) => any;
}) => {
const [selectedApp, navApp, reset] = useNav(props.applications.length);
const ctxh = React.useContext(Context);
@ -53,12 +53,12 @@ export const ApplicationsTable = (props: {
<div className='applications-table argo-table-list argo-table-list--clickable'>
{props.applications.map((app, i) => (
<div
key={app.metadata.name}
key={AppUtils.appInstanceName(app)}
className={`argo-table-list__row
applications-list__entry applications-list__entry--health-${app.status.health.status} ${selectedApp === i ? 'applications-tiles__selected' : ''}`}>
<div
className={`row applications-list__table-row`}
onClick={e => ctx.navigation.goto(`/applications/${app.metadata.name}`, {}, {event: e})}>
onClick={e => ctx.navigation.goto(`/applications/${app.metadata.namespace}/${app.metadata.name}`, {}, {event: e})}>
<div className='columns small-4'>
<div className='row'>
<div className=' columns small-2'>
@ -125,9 +125,9 @@ export const ApplicationsTable = (props: {
</button>
)}
items={[
{title: 'Sync', action: () => props.syncApplication(app.metadata.name)},
{title: 'Refresh', action: () => props.refreshApplication(app.metadata.name)},
{title: 'Delete', action: () => props.deleteApplication(app.metadata.name)}
{title: 'Sync', action: () => props.syncApplication(app.metadata.name, app.metadata.namespace)},
{title: 'Refresh', action: () => props.refreshApplication(app.metadata.name, app.metadata.namespace)},
{title: 'Delete', action: () => props.deleteApplication(app.metadata.name, app.metadata.namespace)}
]}
/>
</div>

View file

@ -14,9 +14,9 @@ require('./applications-tiles.scss');
export interface ApplicationTilesProps {
applications: models.Application[];
syncApplication: (appName: string) => any;
refreshApplication: (appName: string) => any;
deleteApplication: (appName: string) => any;
syncApplication: (appName: string, appNamespace: string) => any;
refreshApplication: (appName: string, appNamespace: string) => any;
deleteApplication: (appName: string, appNamespace: string) => any;
}
const useItemsPerContainer = (itemRef: any, containerRef: any): number => {
@ -109,14 +109,18 @@ export const ApplicationTiles = ({applications, syncApplication, refreshApplicat
className='applications-tiles argo-table-list argo-table-list--clickable row small-up-1 medium-up-2 large-up-3 xxxlarge-up-4'
ref={appContainerRef}>
{applications.map((app, i) => (
<div key={app.metadata.name} className='column column-block'>
<div key={AppUtils.appInstanceName(app)} className='column column-block'>
<div
ref={appRef.set ? null : appRef.ref}
className={`argo-table-list__row applications-list__entry applications-list__entry--health-${app.status.health.status} ${
selectedApp === i ? 'applications-tiles__selected' : ''
}`}>
<div className='row' onClick={e => ctx.navigation.goto(`/applications/${app.metadata.name}`, {view: pref.appDetails.view}, {event: e})}>
<div className={`columns small-12 applications-list__info qe-applications-list-${app.metadata.name}`}>
<div
className='row'
onClick={e =>
ctx.navigation.goto(`/applications/${app.metadata.namespace}/${app.metadata.name}`, {view: pref.appDetails.view}, {event: e})
}>
<div className={`columns small-12 applications-list__info qe-applications-list-${AppUtils.appInstanceName(app)}`}>
<div className='row'>
<div
className={
@ -125,12 +129,12 @@ export const ApplicationTiles = ({applications, syncApplication, refreshApplicat
: 'columns small-11'
}>
<i className={'icon argo-icon-' + (app.spec.source.chart != null ? 'helm' : 'git')} />
{app.metadata.name.length > 30 ? (
<Tooltip content={app.metadata.name}>
<span className='applications-list__title'>{app.metadata.name}</span>
{AppUtils.appQualifiedName(app).length > 30 ? (
<Tooltip content={AppUtils.appInstanceName(app)}>
<span className='applications-list__title'>{AppUtils.appQualifiedName(app)}</span>
</Tooltip>
) : (
<span className='applications-list__title'>{app.metadata.name}</span>
<span className='applications-list__title'>{AppUtils.appQualifiedName(app)}</span>
)}
</div>
<div
@ -261,7 +265,7 @@ export const ApplicationTiles = ({applications, syncApplication, refreshApplicat
qe-id='applications-tiles-button-sync'
onClick={e => {
e.stopPropagation();
syncApplication(app.metadata.name);
syncApplication(app.metadata.name, app.metadata.namespace);
}}>
<i className='fa fa-sync' /> Sync
</a>
@ -272,7 +276,7 @@ export const ApplicationTiles = ({applications, syncApplication, refreshApplicat
{...AppUtils.refreshLinkAttrs(app)}
onClick={e => {
e.stopPropagation();
refreshApplication(app.metadata.name);
refreshApplication(app.metadata.name, app.metadata.namespace);
}}>
<i className={classNames('fa fa-redo', {'status-icon--spin': AppUtils.isAppRefreshing(app)})} />{' '}
<span className='show-for-xxlarge'>Refresh</span>
@ -283,7 +287,7 @@ export const ApplicationTiles = ({applications, syncApplication, refreshApplicat
qe-id='applications-tiles-button-delete'
onClick={e => {
e.stopPropagation();
deleteApplication(app.metadata.name);
deleteApplication(app.metadata.name, app.metadata.namespace);
}}>
<i className='fa fa-times-circle' /> <span className='show-for-xxlarge'>Delete</span>
</a>

View file

@ -15,6 +15,7 @@ import './pod-logs-viewer.scss';
const maxLines = 100;
export interface PodLogsProps {
namespace: string;
applicationNamespace: string;
applicationName: string;
podName?: string;
containerName: string;
@ -112,7 +113,7 @@ export const PodsLogsViewer = (props: PodLogsProps & {fullscreen?: boolean}) =>
};
const fullscreenURL =
`/applications/${props.applicationName}/${props.namespace}/${props.containerName}/logs?` +
`/applications/${props.applicationNamespace}/${props.applicationName}/${props.namespace}/${props.containerName}/logs?` +
`podName=${props.podName}&group=${props.group}&kind=${props.kind}&name=${props.name}`;
return (
<DataLoader load={() => services.viewPreferences.getPreferences()}>
@ -165,6 +166,7 @@ export const PodsLogsViewer = (props: PodLogsProps & {fullscreen?: boolean}) =>
onClick={async () => {
const downloadURL = services.applications.getDownloadLogsURL(
props.applicationName,
props.applicationNamespace,
props.namespace,
props.podName,
{group: props.group, kind: props.kind, name: props.name},
@ -294,6 +296,7 @@ export const PodsLogsViewer = (props: PodLogsProps & {fullscreen?: boolean}) =>
let logsSource = services.applications
.getContainerLogs(
props.applicationName,
props.applicationNamespace,
props.namespace,
props.podName,
{group: props.group, kind: props.kind, name: props.name},

View file

@ -108,6 +108,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => {
name={node.name}
namespace={podState.metadata.namespace}
applicationName={application.metadata.name}
applicationNamespace={application.metadata.namespace}
containerName={AppUtils.getContainerName(podState, activeContainer)}
page={{number: page, untilTimes}}
setPage={pageData => appContext.navigation.goto('.', {page: pageData.number, untilTimes: pageData.untilTimes.join(',')})}
@ -195,7 +196,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => {
input={application.spec}
onSave={async patch => {
const spec = JSON.parse(JSON.stringify(application.spec));
return services.applications.updateSpec(application.metadata.name, jsonMergePatch.apply(spec, JSON.parse(patch)));
return services.applications.updateSpec(application.metadata.name, application.metadata.namespace, jsonMergePatch.apply(spec, JSON.parse(patch)));
}}
/>
)
@ -211,7 +212,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => {
<DataLoader
key='diff'
load={async () =>
await services.applications.managedResources(application.metadata.name, {
await services.applications.managedResources(application.metadata.name, application.metadata.namespace, {
fields: ['items.normalizedLiveState', 'items.predictedLiveState', 'items.group', 'items.kind', 'items.namespace', 'items.name']
})
}>
@ -224,7 +225,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => {
tabs.push({
title: 'EVENTS',
key: 'event',
content: <ApplicationResourceEvents applicationName={application.metadata.name} />
content: <ApplicationResourceEvents applicationName={application.metadata.name} applicationNamespace={application.metadata.namespace} />
});
const extensionTabs = services.extensions.getResourceTabs('argoproj.io', 'Application').map((ext, i) => ({
@ -246,7 +247,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => {
noLoaderOnInputChange={true}
input={selectedNode.resourceVersion}
load={async () => {
const managedResources = await services.applications.managedResources(application.metadata.name, {
const managedResources = await services.applications.managedResources(application.metadata.name, application.metadata.namespace, {
id: {
name: selectedNode.name,
namespace: selectedNode.namespace,
@ -261,10 +262,10 @@ export const ResourceDetails = (props: ResourceDetailsProps) => {
if (controlled && controlled.targetState) {
resQuery.version = AppUtils.parseApiVersion(controlled.targetState.apiVersion).version;
}
const liveState = await services.applications.getResource(application.metadata.name, resQuery).catch(() => null);
const liveState = await services.applications.getResource(application.metadata.name, application.metadata.namespace, resQuery).catch(() => null);
const events =
(liveState &&
(await services.applications.resourceEvents(application.metadata.name, {
(await services.applications.resourceEvents(application.metadata.name, application.metadata.namespace, {
name: liveState.metadata.name,
namespace: liveState.metadata.namespace,
uid: liveState.metadata.uid
@ -276,7 +277,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => {
} else {
const childPod = AppUtils.findChildPod(selectedNode, tree);
if (childPod) {
podState = await services.applications.getResource(application.metadata.name, childPod).catch(() => null);
podState = await services.applications.getResource(application.metadata.name, application.metadata.namespace, childPod).catch(() => null);
}
}

View file

@ -45,7 +45,7 @@ export function helpTip(text: string) {
</Tooltip>
);
}
export async function deleteApplication(appName: string, apis: ContextApis): Promise<boolean> {
export async function deleteApplication(appName: string, appNamespace: string, apis: ContextApis): Promise<boolean> {
let confirmed = false;
const propagationPolicies: {name: string; message: string}[] = [
{
@ -100,7 +100,7 @@ export async function deleteApplication(appName: string, apis: ContextApis): Pro
}),
submit: async (vals, _, close) => {
try {
await services.applications.delete(appName, vals.propagationPolicy);
await services.applications.delete(appName, appNamespace, vals.propagationPolicy);
confirmed = true;
close();
} catch (e) {
@ -288,7 +288,7 @@ export function findChildPod(node: appModels.ResourceNode, tree: appModels.Appli
});
}
export const deletePodAction = async (pod: appModels.Pod, appContext: AppContext, appName: string) => {
export const deletePodAction = async (pod: appModels.Pod, appContext: AppContext, appName: string, appNamespace: string) => {
appContext.apis.popup.prompt(
'Delete pod',
() => (
@ -304,7 +304,7 @@ export const deletePodAction = async (pod: appModels.Pod, appContext: AppContext
{
submit: async (vals, _, close) => {
try {
await services.applications.deleteResource(appName, pod, !!vals.force, false);
await services.applications.deleteResource(appName, appNamespace, pod, !!vals.force, false);
close();
} catch (e) {
appContext.apis.notifications.show({
@ -370,9 +370,9 @@ export const deletePopup = async (ctx: ContextApis, resource: ResourceTreeNode,
const force = deleteOptions.option === 'force';
const orphan = deleteOptions.option === 'orphan';
try {
await services.applications.deleteResource(application.metadata.name, resource, !!force, !!orphan);
await services.applications.deleteResource(application.metadata.name, application.metadata.namespace, resource, !!force, !!orphan);
if (appChanged) {
appChanged.next(await services.applications.get(application.metadata.name));
appChanged.next(await services.applications.get(application.metadata.name, application.metadata.namespace));
}
close();
} catch (e) {
@ -453,7 +453,7 @@ function getActionItems(
.catch(() => items);
const resourceActions = services.applications
.getResourceActions(application.metadata.name, resource)
.getResourceActions(application.metadata.name, application.metadata.namespace, resource)
.then(actions => {
return items.concat(
actions.map(action => ({
@ -463,7 +463,7 @@ function getActionItems(
try {
const confirmed = await appContext.apis.popup.confirm(`Execute '${action.name}' action?`, `Are you sure you want to execute '${action.name}' action?`);
if (confirmed) {
await services.applications.runResourceAction(application.metadata.name, resource, action.name);
await services.applications.runResourceAction(application.metadata.name, application.metadata.namespace, resource, action.name);
}
} catch (e) {
appContext.apis.notifications.show({
@ -1102,3 +1102,11 @@ export const urlPattern = new RegExp(
'gi'
)
);
export function appQualifiedName(app: appModels.Application): string {
return app.metadata.namespace + '/' + app.metadata.name;
}
export function appInstanceName(app: appModels.Application): string {
return app.metadata.namespace + '_' + app.metadata.name;
}

View file

@ -2,7 +2,7 @@ import * as React from 'react';
import {FormFunctionProps} from 'react-form';
import {CheckboxField} from '..';
import * as models from '../../models';
import {ComparisonStatusIcon, HealthStatusIcon, OperationPhaseIcon} from '../../../applications/components/utils';
import {appInstanceName, appQualifiedName, ComparisonStatusIcon, HealthStatusIcon, OperationPhaseIcon} from '../../../applications/components/utils';
export const ApplicationSelector = ({apps, formApi}: {apps: models.Application[]; formApi: FormFunctionProps}) => {
return (
@ -15,10 +15,10 @@ export const ApplicationSelector = ({apps, formApi}: {apps: models.Application[]
</label>
<div style={{marginTop: '0.4em'}}>
{apps.map((app, i) => (
<label key={app.metadata.name} style={{marginTop: '0.5em', cursor: 'pointer'}}>
<label key={appInstanceName(app)} style={{marginTop: '0.5em', cursor: 'pointer'}}>
<CheckboxField field={`app/${i}`} />
&nbsp;
{app.isAppOfAppsPattern ? `(App of Apps) ${app.metadata.name}` : app.metadata.name}
{app.isAppOfAppsPattern ? `(App of Apps) ${appQualifiedName(app)}` : appQualifiedName(app)}
&nbsp;
<ComparisonStatusIcon status={app.status.sync.status} />
&nbsp;

View file

@ -9,11 +9,12 @@ interface QueryOptions {
fields: string[];
exclude?: boolean;
selector?: string;
appNamespace?: string;
}
function optionsToSearch(options?: QueryOptions) {
if (options) {
return {fields: (options.exclude ? '-' : '') + options.fields.join(','), selector: options.selector || ''};
return {fields: (options.exclude ? '-' : '') + options.fields.join(','), selector: options.selector || '', appNamespace: options.appNamespace || ''};
}
return {};
}
@ -30,39 +31,51 @@ export class ApplicationsService {
});
}
public get(name: string, refresh?: 'normal' | 'hard'): Promise<models.Application> {
public get(name: string, appNamespace: string, refresh?: 'normal' | 'hard'): Promise<models.Application> {
const query: {[key: string]: string} = {};
if (refresh) {
query.refresh = refresh;
}
if (appNamespace) {
query.appNamespace = appNamespace;
}
return requests
.get(`/applications/${name}`)
.query(query)
.then(res => this.parseAppFields(res.body));
}
public getApplicationSyncWindowState(name: string): Promise<models.ApplicationSyncWindowState> {
public getApplicationSyncWindowState(name: string, appNamespace: string): Promise<models.ApplicationSyncWindowState> {
return requests
.get(`/applications/${name}/syncwindows`)
.query({name})
.query({name, appNamespace})
.then(res => res.body as models.ApplicationSyncWindowState);
}
public revisionMetadata(name: string, revision: string): Promise<models.RevisionMetadata> {
return requests.get(`/applications/${name}/revisions/${revision || 'HEAD'}/metadata`).then(res => res.body as models.RevisionMetadata);
public revisionMetadata(name: string, appNamespace: string, revision: string): Promise<models.RevisionMetadata> {
return requests
.get(`/applications/${name}/revisions/${revision || 'HEAD'}/metadata`)
.query({appNamespace})
.then(res => res.body as models.RevisionMetadata);
}
public resourceTree(name: string): Promise<models.ApplicationTree> {
return requests.get(`/applications/${name}/resource-tree`).then(res => res.body as models.ApplicationTree);
public resourceTree(name: string, appNamespace: string): Promise<models.ApplicationTree> {
return requests
.get(`/applications/${name}/resource-tree`)
.query({appNamespace})
.then(res => res.body as models.ApplicationTree);
}
public watchResourceTree(name: string): Observable<models.ApplicationTree> {
return requests.loadEventSource(`/stream/applications/${name}/resource-tree`).pipe(map(data => JSON.parse(data).result as models.ApplicationTree));
public watchResourceTree(name: string, appNamespace: string): Observable<models.ApplicationTree> {
return requests
.loadEventSource(`/stream/applications/${name}/resource-tree?appNamespace=${appNamespace}`)
.pipe(map(data => JSON.parse(data).result as models.ApplicationTree));
}
public managedResources(name: string, options: {id?: models.ResourceID; fields?: string[]} = {}): Promise<models.ResourceDiff[]> {
public managedResources(name: string, appNamespace: string, options: {id?: models.ResourceID; fields?: string[]} = {}): Promise<models.ResourceDiff[]> {
return requests
.get(`/applications/${name}/managed-resources`)
.query(`appNamespace=${appNamespace.toString()}`)
.query({...options.id, fields: (options.fields || []).join(',')})
.then(res => (res.body.items as any[]) || [])
.then(items => {
@ -84,14 +97,14 @@ export class ApplicationsService {
});
}
public getManifest(name: string, revision: string): Promise<models.ManifestResponse> {
public getManifest(name: string, appNamespace: string, revision: string): Promise<models.ManifestResponse> {
return requests
.get(`/applications/${name}/manifests`)
.query({name, revision})
.then(res => res.body as models.ManifestResponse);
}
public updateSpec(appName: string, spec: models.ApplicationSpec): Promise<models.ApplicationSpec> {
public updateSpec(appName: string, appNamespace: string, spec: models.ApplicationSpec): Promise<models.ApplicationSpec> {
return requests
.put(`/applications/${appName}/spec`)
.send(spec)
@ -113,7 +126,7 @@ export class ApplicationsService {
.then(res => this.parseAppFields(res.body));
}
public delete(name: string, propagationPolicy: string): Promise<boolean> {
public delete(name: string, appNamespace: string, propagationPolicy: string): Promise<boolean> {
let cascade = true;
if (propagationPolicy === 'non-cascading') {
propagationPolicy = '';
@ -123,13 +136,14 @@ export class ApplicationsService {
.delete(`/applications/${name}`)
.query({
cascade,
propagationPolicy
propagationPolicy,
appNamespace
})
.send({})
.then(() => true);
}
public watch(query?: {name?: string; resourceVersion?: string; projects?: string[]}, options?: QueryOptions): Observable<models.ApplicationWatchEvent> {
public watch(query?: {name?: string; resourceVersion?: string; projects?: string[]; appNamespace?: string}, options?: QueryOptions): Observable<models.ApplicationWatchEvent> {
const search = new URLSearchParams();
if (query) {
if (query.name) {
@ -138,11 +152,15 @@ export class ApplicationsService {
if (query.resourceVersion) {
search.set('resourceVersion', query.resourceVersion);
}
if (query.appNamespace) {
search.set('appNamespace', query.appNamespace);
}
}
if (options) {
const searchOptions = optionsToSearch(options);
search.set('fields', searchOptions.fields);
search.set('selector', searchOptions.selector);
search.set('appNamespace', searchOptions.appNamespace);
query?.projects?.forEach(project => search.append('projects', project));
}
const searchStr = search.toString();
@ -162,6 +180,7 @@ export class ApplicationsService {
public sync(
name: string,
appNamespace: string,
revision: string,
prune: boolean,
dryRun: boolean,
@ -172,25 +191,42 @@ export class ApplicationsService {
): Promise<boolean> {
return requests
.post(`/applications/${name}/sync`)
.send({revision, prune: !!prune, dryRun: !!dryRun, strategy, resources, syncOptions: syncOptions ? {items: syncOptions} : null, retryStrategy})
.send({
appNamespace,
revision,
prune: !!prune,
dryRun: !!dryRun,
strategy,
resources,
syncOptions: syncOptions ? {items: syncOptions} : null,
retryStrategy
})
.then(() => true);
}
public rollback(name: string, id: number): Promise<boolean> {
public rollback(name: string, appNamespace: string, id: number): Promise<boolean> {
return requests
.post(`/applications/${name}/rollback`)
.send({id})
.send({id, appNamespace})
.then(() => true);
}
public getDownloadLogsURL(applicationName: string, namespace: string, podName: string, resource: {group: string; kind: string; name: string}, containerName: string): string {
const search = this.getLogsQuery(namespace, podName, resource, containerName, null, false);
public getDownloadLogsURL(
applicationName: string,
appNamespace: string,
namespace: string,
podName: string,
resource: {group: string; kind: string; name: string},
containerName: string
): string {
const search = this.getLogsQuery(namespace, appNamespace, podName, resource, containerName, null, false);
search.set('download', 'true');
return `api/v1/applications/${applicationName}/logs?${search.toString()}`;
}
public getContainerLogs(
applicationName: string,
appNamespace: string,
namespace: string,
podName: string,
resource: {group: string; kind: string; name: string},
@ -201,7 +237,7 @@ export class ApplicationsService {
filter?: string,
previous?: boolean
): Observable<models.LogEntry> {
const search = this.getLogsQuery(namespace, podName, resource, containerName, tail, follow, untilTime, filter, previous);
const search = this.getLogsQuery(namespace, appNamespace, podName, resource, containerName, tail, follow, untilTime, filter, previous);
const entries = requests.loadEventSource(`/applications/${applicationName}/logs?${search.toString()}`).pipe(map(data => JSON.parse(data).result as models.LogEntry));
let first = true;
return new Observable(observer => {
@ -229,11 +265,12 @@ export class ApplicationsService {
});
}
public getResource(name: string, resource: models.ResourceNode): Promise<models.State> {
public getResource(name: string, appNamespace: string, resource: models.ResourceNode): Promise<models.State> {
return requests
.get(`/applications/${name}/resource`)
.query({
name: resource.name,
appNamespace,
namespace: resource.namespace,
resourceName: resource.name,
version: resource.version,
@ -244,7 +281,7 @@ export class ApplicationsService {
.then(res => JSON.parse(res.manifest) as models.State);
}
public getResourceActions(name: string, resource: models.ResourceNode): Promise<models.ResourceAction[]> {
public getResourceActions(name: string, appNamspace: string, resource: models.ResourceNode): Promise<models.ResourceAction[]> {
return requests
.get(`/applications/${name}/resource/actions`)
.query({
@ -257,7 +294,7 @@ export class ApplicationsService {
.then(res => (res.body.actions as models.ResourceAction[]) || []);
}
public runResourceAction(name: string, resource: models.ResourceNode, action: string): Promise<models.ResourceAction[]> {
public runResourceAction(name: string, appNamspace: string, resource: models.ResourceNode, action: string): Promise<models.ResourceAction[]> {
return requests
.post(`/applications/${name}/resource/actions`)
.query({
@ -271,7 +308,7 @@ export class ApplicationsService {
.then(res => (res.body.actions as models.ResourceAction[]) || []);
}
public patchResource(name: string, resource: models.ResourceNode, patch: string, patchType: string): Promise<models.State> {
public patchResource(name: string, appNamspace: string, resource: models.ResourceNode, patch: string, patchType: string): Promise<models.State> {
return requests
.post(`/applications/${name}/resource`)
.query({
@ -288,11 +325,12 @@ export class ApplicationsService {
.then(res => JSON.parse(res.manifest) as models.State);
}
public deleteResource(applicationName: string, resource: models.ResourceNode, force: boolean, orphan: boolean): Promise<any> {
public deleteResource(applicationName: string, appNamespace: string, resource: models.ResourceNode, force: boolean, orphan: boolean): Promise<any> {
return requests
.delete(`/applications/${applicationName}/resource`)
.query({
name: resource.name,
appNamespace,
namespace: resource.namespace,
resourceName: resource.name,
version: resource.version,
@ -305,15 +343,17 @@ export class ApplicationsService {
.then(() => true);
}
public events(applicationName: string): Promise<models.Event[]> {
public events(applicationName: string, appNamespace: string): Promise<models.Event[]> {
return requests
.get(`/applications/${applicationName}/events`)
.query({appNamespace})
.send()
.then(res => (res.body as models.EventList).items || []);
}
public resourceEvents(
applicationName: string,
appNamespace: string,
resource: {
namespace: string;
name: string;
@ -323,6 +363,7 @@ export class ApplicationsService {
return requests
.get(`/applications/${applicationName}/events`)
.query({
appNamespace,
resourceUID: resource.uid,
resourceNamespace: resource.namespace,
resourceName: resource.name
@ -331,7 +372,7 @@ export class ApplicationsService {
.then(res => (res.body as models.EventList).items || []);
}
public terminateOperation(applicationName: string): Promise<boolean> {
public terminateOperation(applicationName: string, appNamespace: string): Promise<boolean> {
return requests
.delete(`/applications/${applicationName}/operation`)
.send()
@ -340,6 +381,7 @@ export class ApplicationsService {
private getLogsQuery(
namespace: string,
appNamespace: string,
podName: string,
resource: {group: string; kind: string; name: string},
containerName: string,
@ -353,6 +395,7 @@ export class ApplicationsService {
follow = true;
}
const search = new URLSearchParams();
search.set('appNamespace', appNamespace);
search.set('container', containerName);
search.set('namespace', namespace);
search.set('follow', follow.toString());

View file

@ -1,11 +0,0 @@
package app
import (
"fmt"
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
// AppRBACName formats fully qualified application name for RBAC check
func AppRBACName(app appv1.Application) string {
return fmt.Sprintf("%s/%s", app.Spec.GetProject(), app.Name)
}

View file

@ -412,9 +412,18 @@ func GetAppProjectByName(name string, projLister applicationsv1.AppProjectLister
return GetAppVirtualProject(project, projLister, settingsManager)
}
// GetAppProject returns a project from an application
func GetAppProject(spec *argoappv1.ApplicationSpec, projLister applicationsv1.AppProjectLister, ns string, settingsManager *settings.SettingsManager, db db.ArgoDB, ctx context.Context) (*argoappv1.AppProject, error) {
return GetAppProjectByName(spec.GetProject(), projLister, ns, settingsManager, db, ctx)
// GetAppProject returns a project from an application. It will also ensure
// that the application is allowed to use the project.
func GetAppProject(app *argoappv1.Application, projLister applicationsv1.AppProjectLister, ns string, settingsManager *settings.SettingsManager, db db.ArgoDB, ctx context.Context) (*argoappv1.AppProject, error) {
proj, err := GetAppProjectByName(app.Spec.GetProject(), projLister, ns, settingsManager, db, ctx)
if err != nil {
return nil, err
}
if !proj.IsAppNamespacePermitted(app, ns) {
return nil, fmt.Errorf("application '%s' in namespace '%s' is not allowed to use project '%s'",
app.Name, app.Namespace, proj.Name)
}
return proj, nil
}
// verifyGenerateManifests verifies a repo path can generate manifests
@ -661,3 +670,55 @@ func GetDifferentPathsBetweenStructs(a, b interface{}) ([]string, error) {
}
return difference, nil
}
// parseAppName will
func parseAppName(appName string, defaultNs string, delim string) (string, string) {
var ns string
var name string
t := strings.SplitN(appName, delim, 2)
if len(t) == 2 {
ns = t[0]
name = t[1]
} else {
ns = defaultNs
name = t[0]
}
return name, ns
}
// ParseAppNamespacedName parses a namespaced name in the format namespace/name
// and returns the components. If name wasn't namespaced, defaultNs will be
// returned as namespace component.
func ParseAppQualifiedName(appName string, defaultNs string) (string, string) {
return parseAppName(appName, defaultNs, "/")
}
// ParseAppInstanceName parses a namespaced name in the format namespace_name
// and returns the components. If name wasn't namespaced, defaultNs will be
// returned as namespace component.
func ParseAppInstanceName(appName string, defaultNs string) (string, string) {
return parseAppName(appName, defaultNs, "_")
}
// AppInstanceName returns the value to be used for app instance labels from
// the combination of appName, appNs and defaultNs.
func AppInstanceName(appName, appNs, defaultNs string) string {
if appNs == "" || appNs == defaultNs {
return appName
} else {
return appNs + "_" + appName
}
}
// AppInstanceNameFromQualified returns the value to be used for app
func AppInstanceNameFromQualified(name string, defaultNs string) string {
appName, appNs := ParseAppQualifiedName(name, defaultNs)
return AppInstanceName(appName, appNs, defaultNs)
}
// ErrProjectNotPermitted returns an error to indicate that an application
// identified by appName and appNamespace is not allowed to use the project
// identified by projName.
func ErrProjectNotPermitted(appName, appNamespace, projName string) error {
return fmt.Errorf("application '%s' in namespace '%s' is not permitted to use project '%s'", appName, appNamespace, projName)
}

View file

@ -77,7 +77,7 @@ func TestGetAppProjectWithNoProjDefined(t *testing.T) {
kubeClient := fake.NewSimpleClientset(&cm)
settingsMgr := settings.NewSettingsManager(context.Background(), kubeClient, test.FakeArgoCDNamespace)
argoDB := db.NewDB("default", settingsMgr, kubeClient)
proj, err := GetAppProject(&testApp.Spec, applisters.NewAppProjectLister(informer.GetIndexer()), namespace, settingsMgr, argoDB, ctx)
proj, err := GetAppProject(&testApp, applisters.NewAppProjectLister(informer.GetIndexer()), namespace, settingsMgr, argoDB, ctx)
assert.Nil(t, err)
assert.Equal(t, proj.Name, projName)
}
@ -924,3 +924,95 @@ func Test_GenerateSpecIsDifferentErrorMessageWithDiff(t *testing.T) {
assert.Equal(t, msg, "existing repo spec is different; use upsert flag to force update; difference in keys \"Name\"")
}
func Test_ParseAppQualifiedName(t *testing.T) {
testcases := []struct {
name string
input string
implicitNs string
appName string
appNs string
}{
{"Full qualified without implicit NS", "namespace/name", "", "name", "namespace"},
{"Non qualified without implicit NS", "name", "", "name", ""},
{"Full qualified with implicit NS", "namespace/name", "namespace2", "name", "namespace"},
{"Non qualified with implicit NS", "name", "namespace2", "name", "namespace2"},
{"Invalid without implicit NS", "namespace_name", "", "namespace_name", ""},
}
for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
appName, appNs := ParseAppQualifiedName(tt.input, tt.implicitNs)
assert.Equal(t, tt.appName, appName)
assert.Equal(t, tt.appNs, appNs)
})
}
}
func Test_ParseAppInstanceName(t *testing.T) {
testcases := []struct {
name string
input string
implicitNs string
appName string
appNs string
}{
{"Full qualified without implicit NS", "namespace_name", "", "name", "namespace"},
{"Non qualified without implicit NS", "name", "", "name", ""},
{"Full qualified with implicit NS", "namespace_name", "namespace2", "name", "namespace"},
{"Non qualified with implicit NS", "name", "namespace2", "name", "namespace2"},
{"Invalid without implicit NS", "namespace/name", "", "namespace/name", ""},
}
for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
appName, appNs := ParseAppInstanceName(tt.input, tt.implicitNs)
assert.Equal(t, tt.appName, appName)
assert.Equal(t, tt.appNs, appNs)
})
}
}
func Test_AppInstanceName(t *testing.T) {
testcases := []struct {
name string
appName string
appNamespace string
defaultNs string
result string
}{
{"defaultns different as appns", "appname", "appns", "defaultns", "appns_appname"},
{"defaultns same as appns", "appname", "appns", "appns", "appname"},
{"defaultns set and appns not given", "appname", "", "appns", "appname"},
{"neither defaultns nor appns set", "appname", "", "appns", "appname"},
}
for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
result := AppInstanceName(tt.appName, tt.appNamespace, tt.defaultNs)
assert.Equal(t, tt.result, result)
})
}
}
func Test_AppInstanceNameFromQualified(t *testing.T) {
testcases := []struct {
name string
appName string
defaultNs string
result string
}{
{"Qualified name with namespace not being defaultns", "appns/appname", "defaultns", "appns_appname"},
{"Qualified name with namespace being defaultns", "defaultns/appname", "defaultns", "appname"},
{"Qualified name without namespace", "appname", "defaultns", "appname"},
{"Qualified name without namespace and defaultns", "appname", "", "appname"},
}
for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
result := AppInstanceNameFromQualified(tt.appName, tt.defaultNs)
assert.Equal(t, tt.result, result)
})
}
}

57
util/glob/glob_test.go Normal file
View file

@ -0,0 +1,57 @@
package glob
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Match(t *testing.T) {
tests := []struct {
name string
input string
pattern string
result bool
}{
{"Exact match", "hello", "hello", true},
{"Non-match exact", "hello", "hell", false},
{"Long glob match", "hello", "hell*", true},
{"Short glob match", "hello", "h*", true},
{"Glob non-match", "hello", "e*", false},
{"Invalid pattern", "e[[a*", "e[[a*", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res := Match(tt.pattern, tt.input)
assert.Equal(t, tt.result, res)
})
}
}
func Test_MatchList(t *testing.T) {
tests := []struct {
name string
input string
list []string
exact bool
result bool
}{
{"Exact name in list", "test", []string{"test"}, true, true},
{"Exact name not in list", "test", []string{"other"}, true, false},
{"Exact name not in list, multiple elements", "test", []string{"some", "other"}, true, false},
{"Exact name not in list, list empty", "test", []string{}, true, false},
{"Exact name not in list, empty element", "test", []string{""}, true, false},
{"Glob name in list, but exact wanted", "test", []string{"*"}, true, false},
{"Glob name in list with simple wildcard", "test", []string{"*"}, false, true},
{"Glob name in list without wildcard", "test", []string{"test"}, false, true},
{"Glob name in list, multiple elements", "test", []string{"other*", "te*"}, false, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res := MatchStringInList(tt.list, tt.input, tt.exact)
assert.Equal(t, tt.result, res)
})
}
}

12
util/glob/list.go Normal file
View file

@ -0,0 +1,12 @@
package glob
// MatchStringInList will return true if item is contained in list. If
// exactMatch is set to false, list may contain globs to be matched.
func MatchStringInList(list []string, item string, exactMatch bool) bool {
for _, ll := range list {
if item == ll || (!exactMatch && Match(ll, item)) {
return true
}
}
return false
}

View file

@ -1883,3 +1883,7 @@ func (mgr *SettingsManager) GetGlobalProjectsSettings() ([]GlobalProjectSettings
}
return globalProjectSettings, nil
}
func (mgr *SettingsManager) GetNamespace() string {
return mgr.namespace
}