diff --git a/.github/workflows/ci-build.yaml b/.github/workflows/ci-build.yaml index 3a596a9552d70..e01964e1e6a60 100644 --- a/.github/workflows/ci-build.yaml +++ b/.github/workflows/ci-build.yaml @@ -1,5 +1,5 @@ name: Integration tests -on: +on: push: branches: - 'master' @@ -23,9 +23,28 @@ permissions: contents: read jobs: + changes: + runs-on: ubuntu-latest + outputs: + backend: ${{ steps.filter.outputs.backend }} + frontend: ${{ steps.filter.outputs.frontend }} + steps: + - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + - uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2 + id: filter + with: + filters: | + backend: + - '!(ui/**)' + - '!(**/*.md)' + frontend: + - 'ui/**' check-go: name: Ensure Go modules synchronicity + if: ${{ needs.changes.outputs.backend == 'true' }} runs-on: ubuntu-22.04 + needs: + - changes steps: - name: Checkout code uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 @@ -43,7 +62,10 @@ jobs: build-go: name: Build & cache Go code + if: ${{ needs.changes.outputs.backend == 'true' }} runs-on: ubuntu-22.04 + needs: + - changes steps: - name: Checkout code uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 @@ -67,7 +89,10 @@ jobs: contents: read # for actions/checkout to fetch code pull-requests: read # for golangci/golangci-lint-action to fetch pull requests name: Lint Go code + if: ${{ needs.changes.outputs.backend == 'true' }} runs-on: ubuntu-22.04 + needs: + - changes steps: - name: Checkout code uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 @@ -83,12 +108,14 @@ jobs: test-go: name: Run unit tests for Go packages + if: ${{ needs.changes.outputs.backend == 'true' }} runs-on: ubuntu-22.04 needs: - build-go + - changes env: GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }} + GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }} steps: - name: Create checkout directory run: mkdir -p ~/go/src/github.com/argoproj @@ -150,12 +177,14 @@ jobs: test-go-race: name: Run unit tests with -race for Go packages + if: ${{ needs.changes.outputs.backend == 'true' }} runs-on: ubuntu-22.04 needs: - build-go + - changes env: GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }} + GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }} steps: - name: Create checkout directory run: mkdir -p ~/go/src/github.com/argoproj @@ -212,7 +241,10 @@ jobs: codegen: name: Check changes to generated code + if: ${{ needs.changes.outputs.backend == 'true' }} runs-on: ubuntu-22.04 + needs: + - changes steps: - name: Checkout code uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 @@ -260,7 +292,10 @@ jobs: build-ui: name: Build, test & lint UI code + if: ${{ needs.changes.outputs.frontend == 'true' }} runs-on: ubuntu-22.04 + needs: + - changes steps: - name: Checkout code uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 @@ -292,10 +327,12 @@ jobs: analyze: name: Process & analyze test artifacts + if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' }} runs-on: ubuntu-22.04 needs: - test-go - build-ui + - changes env: sonar_secret: ${{ secrets.SONAR_TOKEN }} steps: @@ -315,7 +352,7 @@ jobs: - name: Create test-results directory run: | mkdir -p test-results - - name: Get code coverage artifiact + - name: Get code coverage artifact uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 with: name: code-coverage @@ -336,35 +373,37 @@ jobs: SCANNER_PATH: /tmp/cache/scanner OS: linux run: | - # We do not use the provided action, because it does contain an old - # version of the scanner, and also takes time to build. - set -e - mkdir -p ${SCANNER_PATH} - export SONAR_USER_HOME=${SCANNER_PATH}/.sonar - if [[ ! -x "${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner" ]]; then - curl -Ol https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SCANNER_VERSION}-${OS}.zip - unzip -qq -o sonar-scanner-cli-${SCANNER_VERSION}-${OS}.zip -d ${SCANNER_PATH} - fi - - chmod +x ${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner - chmod +x ${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/jre/bin/java - - # Explicitly set NODE_MODULES - export NODE_MODULES=${PWD}/ui/node_modules - export NODE_PATH=${PWD}/ui/node_modules - - ${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner + # We do not use the provided action, because it does contain an old + # version of the scanner, and also takes time to build. + set -e + mkdir -p ${SCANNER_PATH} + export SONAR_USER_HOME=${SCANNER_PATH}/.sonar + if [[ ! -x "${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner" ]]; then + curl -Ol https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SCANNER_VERSION}-${OS}.zip + unzip -qq -o sonar-scanner-cli-${SCANNER_VERSION}-${OS}.zip -d ${SCANNER_PATH} + fi + + chmod +x ${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner + chmod +x ${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/jre/bin/java + + # Explicitly set NODE_MODULES + export NODE_MODULES=${PWD}/ui/node_modules + export NODE_PATH=${PWD}/ui/node_modules + + ${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner if: env.sonar_secret != '' test-e2e: name: Run end-to-end tests + if: ${{ needs.changes.outputs.backend == 'true' }} runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: - k3s-version: [v1.28.2, v1.27.6, v1.26.9, v1.25.14] - needs: + k3s-version: [v1.29.1, v1.28.6, v1.27.10, v1.26.13, v1.25.16] + needs: - build-go + - changes env: GOPATH: /home/runner/go ARGOCD_FAKE_IN_CLUSTER: "true" @@ -377,7 +416,7 @@ jobs: ARGOCD_APPLICATION_NAMESPACES: "argocd-e2e-external,argocd-e2e-external-2" ARGOCD_SERVER: "127.0.0.1:8088" GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }} + GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }} steps: - name: Checkout code uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 @@ -462,3 +501,26 @@ jobs: name: e2e-server-k8s${{ matrix.k3s-version }}.log path: /tmp/e2e-server.log if: ${{ failure() }} + + # workaround for status checks -- check this one job instead of each individual E2E job in the matrix + # this allows us to skip the entire matrix when it doesn't need to run while still having accurate status checks + # see: + # https://github.com/argoproj/argo-workflows/pull/12006 + # https://github.com/orgs/community/discussions/9141#discussioncomment-2296809 + # https://github.com/orgs/community/discussions/26822#discussioncomment-3305794 + test-e2e-composite-result: + name: E2E Tests - Composite result + if: ${{ always() }} + needs: + - test-e2e + - changes + runs-on: ubuntu-22.04 + steps: + - run: | + result="${{ needs.test-e2e.result }}" + # mark as successful even if skipped + if [[ $result == "success" || $result == "skipped" ]]; then + exit 0 + else + exit 1 + fi \ No newline at end of file diff --git a/CODEOWNERS b/CODEOWNERS index ec72eccbf416e..83bb38871d96d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -2,9 +2,10 @@ ** @argoproj/argocd-approvers # Docs -/docs/** @argoproj/argocd-approvers @argoproj/argocd-approvers-docs -/USERS.md @argoproj/argocd-approvers @argoproj/argocd-approvers-docs -/mkdocs.yml @argoproj/argocd-approvers @argoproj/argocd-approvers-docs +/docs/** @argoproj/argocd-approvers @argoproj/argocd-approvers-docs +/USERS.md @argoproj/argocd-approvers @argoproj/argocd-approvers-docs +/README.md @argoproj/argocd-approvers @argoproj/argocd-approvers-docs +/mkdocs.yml @argoproj/argocd-approvers @argoproj/argocd-approvers-docs # CI /.github/** @argoproj/argocd-approvers @argoproj/argocd-approvers-ci diff --git a/Makefile b/Makefile index 8bd9a49b6bb02..a4d6bd5264624 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,7 @@ ARGOCD_E2E_DEX_PORT?=5556 ARGOCD_E2E_YARN_HOST?=localhost ARGOCD_E2E_DISABLE_AUTH?= -ARGOCD_E2E_TEST_TIMEOUT?=60m +ARGOCD_E2E_TEST_TIMEOUT?=90m ARGOCD_IN_CI?=false ARGOCD_TEST_E2E?=true diff --git a/Procfile b/Procfile index 3bc2de5eca5e0..4862b0230062f 100644 --- a/Procfile +++ b/Procfile @@ -1,4 +1,4 @@ -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:-''} --server-side-diff-enabled=${ARGOCD_APPLICATION_CONTROLLER_SERVER_SIDE_DIFF:-'false'}" +controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "HOSTNAME=testappcontroller-1 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:-''} --server-side-diff-enabled=${ARGOCD_APPLICATION_CONTROLLER_SERVER_SIDE_DIFF:-'false'}" 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 && (test -f dist/dex.yaml || { echo 'Failed to generate dex configuration'; exit 1; }) && 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} docker.io/library/redis:$(grep "image: redis" manifests/base/redis/argocd-redis-deployment.yaml | cut -d':' -f3) --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; fi" diff --git a/README.md b/README.md index ef5664de5b5b7..707848191c830 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ **Social:** [![Twitter Follow](https://img.shields.io/twitter/follow/argoproj?style=social)](https://twitter.com/argoproj) [![Slack](https://img.shields.io/badge/slack-argoproj-brightgreen.svg?logo=slack)](https://argoproj.github.io/community/join-slack) +[![LinkedIn](https://img.shields.io/badge/LinkedIn-argoproj-blue.svg?logo=linkedin)](https://www.linkedin.com/company/argoproj/) # Argo CD - Declarative Continuous Delivery for Kubernetes @@ -85,4 +86,5 @@ Participation in the Argo CD project is governed by the [CNCF Code of Conduct](h 1. [Getting Started with ArgoCD for GitOps Deployments](https://youtu.be/AvLuplh1skA) 1. [Using Argo CD & Datree for Stable Kubernetes CI/CD Deployments](https://youtu.be/17894DTru2Y) 1. [How to create Argo CD Applications Automatically using ApplicationSet? "Automation of GitOps"](https://amralaayassen.medium.com/how-to-create-argocd-applications-automatically-using-applicationset-automation-of-the-gitops-59455eaf4f72) +1. [Progressive Delivery with Service Mesh – Argo Rollouts with Istio](https://www.cncf.io/blog/2022/12/16/progressive-delivery-with-service-mesh-argo-rollouts-with-istio/) diff --git a/USERS.md b/USERS.md index 60dd3b881c10b..3f164796d099f 100644 --- a/USERS.md +++ b/USERS.md @@ -94,6 +94,7 @@ Currently, the following organizations are **officially** using Argo CD: 1. [Fave](https://myfave.com) 1. [Flexport](https://www.flexport.com/) 1. [Flip](https://flip.id) +1. [Fly Security](https://www.flysecurity.com.br/) 1. [Fonoa](https://www.fonoa.com/) 1. [Fortra](https://www.fortra.com) 1. [freee](https://corp.freee.co.jp/en/company/) @@ -220,6 +221,7 @@ Currently, the following organizations are **officially** using Argo CD: 1. [Pigment](https://www.gopigment.com/) 1. [Pipefy](https://www.pipefy.com/) 1. [Pismo](https://pismo.io/) +1. [PITS Globale Datenrettungsdienste](https://www.pitsdatenrettung.de/) 1. [Platform9 Systems](https://platform9.com/) 1. [Polarpoint.io](https://polarpoint.io) 1. [PostFinance](https://github.com/postfinance) @@ -282,6 +284,7 @@ Currently, the following organizations are **officially** using Argo CD: 1. [Tamkeen Technologies](https://tamkeentech.sa/) 1. [Techcombank](https://www.techcombank.com.vn/trang-chu) 1. [Technacy](https://www.technacy.it/) +1. [Telavita](https://www.telavita.com.br/) 1. [Tesla](https://tesla.com/) 1. [The Scale Factory](https://www.scalefactory.com/) 1. [ThousandEyes](https://www.thousandeyes.com/) diff --git a/assets/swagger.json b/assets/swagger.json index a9f45fcbb1956..91e815203eee0 100644 --- a/assets/swagger.json +++ b/assets/swagger.json @@ -5664,6 +5664,10 @@ "type": "string", "title": "ClusterName contains AWS cluster name" }, + "profile": { + "description": "Profile contains optional role ARN. If set then AWS IAM Authenticator uses the profile to perform cluster operations instead of the default AWS credential provider chain.", + "type": "string" + }, "roleARN": { "description": "RoleARN contains optional role ARN. If set then AWS IAM Authenticator assume a role to perform cluster operations instead of the default AWS credential provider chain.", "type": "string" diff --git a/cmd/argocd-application-controller/commands/argocd_application_controller.go b/cmd/argocd-application-controller/commands/argocd_application_controller.go index d5ef88a1702b6..0ff9fa33c8254 100644 --- a/cmd/argocd-application-controller/commands/argocd_application_controller.go +++ b/cmd/argocd-application-controller/commands/argocd_application_controller.go @@ -50,6 +50,7 @@ func NewCommand() *cobra.Command { clientConfig clientcmd.ClientConfig appResyncPeriod int64 appHardResyncPeriod int64 + appResyncJitter int64 repoErrorGracePeriod int64 repoServerAddress string repoServerTimeoutSeconds int @@ -146,7 +147,7 @@ func NewCommand() *cobra.Command { appController.InvalidateProjectsCache() })) kubectl := kubeutil.NewKubectl() - clusterFilter := getClusterFilter(kubeClient, settingsMgr, shardingAlgorithm, enableDynamicClusterDistribution) + clusterSharding := getClusterSharding(kubeClient, settingsMgr, shardingAlgorithm, enableDynamicClusterDistribution) appController, err = controller.NewApplicationController( namespace, settingsMgr, @@ -157,6 +158,7 @@ func NewCommand() *cobra.Command { kubectl, resyncDuration, hardResyncDuration, + time.Duration(appResyncJitter)*time.Second, time.Duration(selfHealTimeoutSeconds)*time.Second, time.Duration(repoErrorGracePeriod)*time.Second, metricsPort, @@ -164,7 +166,7 @@ func NewCommand() *cobra.Command { metricsAplicationLabels, kubectlParallelismLimit, persistResourceHealth, - clusterFilter, + clusterSharding, applicationNamespaces, &workqueueRateLimit, serverSideDiff, @@ -194,6 +196,7 @@ func NewCommand() *cobra.Command { clientConfig = cli.AddKubectlFlagsToCmd(&command) command.Flags().Int64Var(&appResyncPeriod, "app-resync", int64(env.ParseDurationFromEnv("ARGOCD_RECONCILIATION_TIMEOUT", defaultAppResyncPeriod*time.Second, 0, math.MaxInt64).Seconds()), "Time period in seconds for application resync.") command.Flags().Int64Var(&appHardResyncPeriod, "app-hard-resync", int64(env.ParseDurationFromEnv("ARGOCD_HARD_RECONCILIATION_TIMEOUT", defaultAppHardResyncPeriod*time.Second, 0, math.MaxInt64).Seconds()), "Time period in seconds for application hard resync.") + command.Flags().Int64Var(&appResyncJitter, "app-resync-jitter", int64(env.ParseDurationFromEnv("ARGOCD_RECONCILIATION_JITTER", 0*time.Second, 0, math.MaxInt64).Seconds()), "Maximum time period in seconds to add as a delay jitter for application resync.") command.Flags().Int64Var(&repoErrorGracePeriod, "repo-error-grace-period-seconds", int64(env.ParseDurationFromEnv("ARGOCD_REPO_ERROR_GRACE_PERIOD_SECONDS", defaultAppResyncPeriod*time.Second, 0, math.MaxInt64).Seconds()), "Grace period in seconds for ignoring consecutive errors while communicating with repo server.") command.Flags().StringVar(&repoServerAddress, "repo-server", env.StringFromEnv("ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER", common.DefaultRepoServerAddr), "Repo server address.") command.Flags().IntVar(&repoServerTimeoutSeconds, "repo-server-timeout-seconds", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_TIMEOUT_SECONDS", 60, 0, math.MaxInt64), "Repo server RPC call timeout seconds.") @@ -235,11 +238,10 @@ func NewCommand() *cobra.Command { return &command } -func getClusterFilter(kubeClient *kubernetes.Clientset, settingsMgr *settings.SettingsManager, shardingAlgorithm string, enableDynamicClusterDistribution bool) sharding.ClusterFilterFunction { - - var replicas int - shard := env.ParseNumFromEnv(common.EnvControllerShard, -1, -math.MaxInt32, math.MaxInt32) - +func getClusterSharding(kubeClient *kubernetes.Clientset, settingsMgr *settings.SettingsManager, shardingAlgorithm string, enableDynamicClusterDistribution bool) sharding.ClusterShardingCache { + var replicasCount int + // StatefulSet mode and Deployment mode uses different default values for shard number. + defaultShardNumberValue := 0 applicationControllerName := env.StringFromEnv(common.EnvAppControllerName, common.DefaultApplicationControllerName) appControllerDeployment, err := kubeClient.AppsV1().Deployments(settingsMgr.GetNamespace()).Get(context.Background(), applicationControllerName, metav1.GetOptions{}) @@ -249,23 +251,22 @@ func getClusterFilter(kubeClient *kubernetes.Clientset, settingsMgr *settings.Se } if enableDynamicClusterDistribution && appControllerDeployment != nil && appControllerDeployment.Spec.Replicas != nil { - replicas = int(*appControllerDeployment.Spec.Replicas) + replicasCount = int(*appControllerDeployment.Spec.Replicas) + defaultShardNumberValue = -1 } else { - replicas = env.ParseNumFromEnv(common.EnvControllerReplicas, 0, 0, math.MaxInt32) + replicasCount = env.ParseNumFromEnv(common.EnvControllerReplicas, 0, 0, math.MaxInt32) } - - var clusterFilter func(cluster *v1alpha1.Cluster) bool - if replicas > 1 { + shardNumber := env.ParseNumFromEnv(common.EnvControllerShard, defaultShardNumberValue, -math.MaxInt32, math.MaxInt32) + if replicasCount > 1 { // check for shard mapping using configmap if application-controller is a deployment // else use existing logic to infer shard from pod name if application-controller is a statefulset if enableDynamicClusterDistribution && appControllerDeployment != nil { - var err error // retry 3 times if we find a conflict while updating shard mapping configMap. // If we still see conflicts after the retries, wait for next iteration of heartbeat process. for i := 0; i <= common.AppControllerHeartbeatUpdateRetryCount; i++ { - shard, err = sharding.GetOrUpdateShardFromConfigMap(kubeClient, settingsMgr, replicas, shard) - if !kubeerrors.IsConflict(err) { + shardNumber, err = sharding.GetOrUpdateShardFromConfigMap(kubeClient, settingsMgr, replicasCount, shardNumber) + if err != nil && !kubeerrors.IsConflict(err) { err = fmt.Errorf("unable to get shard due to error updating the sharding config map: %s", err) break } @@ -273,19 +274,19 @@ func getClusterFilter(kubeClient *kubernetes.Clientset, settingsMgr *settings.Se } errors.CheckError(err) } else { - if shard < 0 { + if shardNumber < 0 { var err error - shard, err = sharding.InferShard() + shardNumber, err = sharding.InferShard() errors.CheckError(err) } + if shardNumber > replicasCount { + log.Warnf("Calculated shard number %d is greated than the number of replicas count. Defaulting to 0", shardNumber) + shardNumber = 0 + } } - log.Infof("Processing clusters from shard %d", shard) - db := db.NewDB(settingsMgr.GetNamespace(), settingsMgr, kubeClient) - log.Infof("Using filter function: %s", shardingAlgorithm) - distributionFunction := sharding.GetDistributionFunction(db, shardingAlgorithm) - clusterFilter = sharding.GetClusterFilter(db, distributionFunction, shard) } else { log.Info("Processing all cluster shards") } - return clusterFilter + db := db.NewDB(settingsMgr.GetNamespace(), settingsMgr, kubeClient) + return sharding.NewClusterSharding(db, shardNumber, replicasCount, shardingAlgorithm) } diff --git a/cmd/argocd-k8s-auth/commands/aws.go b/cmd/argocd-k8s-auth/commands/aws.go index 79a118d2653a3..9b750ac5f92f8 100644 --- a/cmd/argocd-k8s-auth/commands/aws.go +++ b/cmd/argocd-k8s-auth/commands/aws.go @@ -37,13 +37,14 @@ func newAWSCommand() *cobra.Command { var ( clusterName string roleARN string + profile string ) var command = &cobra.Command{ Use: "aws", Run: func(c *cobra.Command, args []string) { ctx := c.Context() - presignedURLString, err := getSignedRequestWithRetry(ctx, time.Minute, 5*time.Second, clusterName, roleARN, getSignedRequest) + presignedURLString, err := getSignedRequestWithRetry(ctx, time.Minute, 5*time.Second, clusterName, roleARN, profile, getSignedRequest) errors.CheckError(err) token := v1Prefix + base64.RawURLEncoding.EncodeToString([]byte(presignedURLString)) // Set token expiration to 1 minute before the presigned URL expires for some cushion @@ -53,16 +54,17 @@ func newAWSCommand() *cobra.Command { } command.Flags().StringVar(&clusterName, "cluster-name", "", "AWS Cluster name") command.Flags().StringVar(&roleARN, "role-arn", "", "AWS Role ARN") + command.Flags().StringVar(&profile, "profile", "", "AWS Profile") return command } -type getSignedRequestFunc func(clusterName, roleARN string) (string, error) +type getSignedRequestFunc func(clusterName, roleARN string, profile string) (string, error) -func getSignedRequestWithRetry(ctx context.Context, timeout, interval time.Duration, clusterName, roleARN string, fn getSignedRequestFunc) (string, error) { +func getSignedRequestWithRetry(ctx context.Context, timeout, interval time.Duration, clusterName, roleARN string, profile string, fn getSignedRequestFunc) (string, error) { ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() for { - signed, err := fn(clusterName, roleARN) + signed, err := fn(clusterName, roleARN, profile) if err == nil { return signed, nil } @@ -74,8 +76,10 @@ func getSignedRequestWithRetry(ctx context.Context, timeout, interval time.Durat } } -func getSignedRequest(clusterName, roleARN string) (string, error) { - sess, err := session.NewSession() +func getSignedRequest(clusterName, roleARN string, profile string) (string, error) { + sess, err := session.NewSessionWithOptions(session.Options{ + Profile: profile, + }) if err != nil { return "", fmt.Errorf("error creating new AWS session: %s", err) } diff --git a/cmd/argocd-k8s-auth/commands/aws_test.go b/cmd/argocd-k8s-auth/commands/aws_test.go index c22449eba42be..578aae71a2c29 100644 --- a/cmd/argocd-k8s-auth/commands/aws_test.go +++ b/cmd/argocd-k8s-auth/commands/aws_test.go @@ -22,7 +22,7 @@ func TestGetSignedRequestWithRetry(t *testing.T) { } // when - signed, err := getSignedRequestWithRetry(ctx, time.Second, time.Millisecond, "cluster-name", "", mock.getSignedRequestMock) + signed, err := getSignedRequestWithRetry(ctx, time.Second, time.Millisecond, "cluster-name", "", "", mock.getSignedRequestMock) // then assert.NoError(t, err) @@ -41,7 +41,7 @@ func TestGetSignedRequestWithRetry(t *testing.T) { } // when - signed, err := getSignedRequestWithRetry(ctx, time.Second, time.Millisecond, "cluster-name", "", mock.getSignedRequestMock) + signed, err := getSignedRequestWithRetry(ctx, time.Second, time.Millisecond, "cluster-name", "", "", mock.getSignedRequestMock) // then assert.NoError(t, err) @@ -57,7 +57,7 @@ func TestGetSignedRequestWithRetry(t *testing.T) { } // when - signed, err := getSignedRequestWithRetry(ctx, time.Second, time.Millisecond, "cluster-name", "", mock.getSignedRequestMock) + signed, err := getSignedRequestWithRetry(ctx, time.Second, time.Millisecond, "cluster-name", "", "", mock.getSignedRequestMock) // then assert.Error(t, err) @@ -70,7 +70,7 @@ type signedRequestMock struct { returnFunc func(m *signedRequestMock) (string, error) } -func (m *signedRequestMock) getSignedRequestMock(clusterName, roleARN string) (string, error) { +func (m *signedRequestMock) getSignedRequestMock(clusterName, roleARN string, profile string) (string, error) { m.getSignedRequestCalls++ return m.returnFunc(m) } diff --git a/cmd/argocd-server/commands/argocd_server.go b/cmd/argocd-server/commands/argocd_server.go index 72fe765c32c56..646ecd6a2aabe 100644 --- a/cmd/argocd-server/commands/argocd_server.go +++ b/cmd/argocd-server/commands/argocd_server.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math" + "strings" "time" "github.com/argoproj/pkg/stats" @@ -63,6 +64,7 @@ func NewCommand() *cobra.Command { repoServerAddress string dexServerAddress string disableAuth bool + contentTypes string enableGZip bool tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error) cacheSrc func() (*servercache.Cache, error) @@ -170,6 +172,11 @@ func NewCommand() *cobra.Command { baseHRef = rootPath } + var contentTypesList []string + if contentTypes != "" { + contentTypesList = strings.Split(contentTypes, ";") + } + argoCDOpts := server.ArgoCDServerOpts{ Insecure: insecure, ListenPort: listenPort, @@ -185,6 +192,7 @@ func NewCommand() *cobra.Command { DexServerAddr: dexServerAddress, DexTLSConfig: dexTlsConfig, DisableAuth: disableAuth, + ContentTypes: contentTypesList, EnableGZip: enableGZip, TLSConfigCustomizer: tlsConfigCustomizer, Cache: cache, @@ -240,6 +248,7 @@ func NewCommand() *cobra.Command { command.Flags().StringVar(&repoServerAddress, "repo-server", env.StringFromEnv("ARGOCD_SERVER_REPO_SERVER", common.DefaultRepoServerAddr), "Repo server address") command.Flags().StringVar(&dexServerAddress, "dex-server", env.StringFromEnv("ARGOCD_SERVER_DEX_SERVER", common.DefaultDexServerAddr), "Dex server address") command.Flags().BoolVar(&disableAuth, "disable-auth", env.ParseBoolFromEnv("ARGOCD_SERVER_DISABLE_AUTH", false), "Disable client authentication") + command.Flags().StringVar(&contentTypes, "api-content-types", env.StringFromEnv("ARGOCD_API_CONTENT_TYPES", "application/json"), "Semicolon separated list of allowed content types for non GET api requests. Any content type is allowed if empty.") command.Flags().BoolVar(&enableGZip, "enable-gzip", env.ParseBoolFromEnv("ARGOCD_SERVER_ENABLE_GZIP", true), "Enable GZIP compression") command.AddCommand(cli.NewVersionCmd(cliName)) command.Flags().StringVar(&listenHost, "address", env.StringFromEnv("ARGOCD_SERVER_LISTEN_ADDRESS", common.DefaultAddressAPIServer), "Listen on given address") diff --git a/cmd/argocd/commands/admin/cluster.go b/cmd/argocd/commands/admin/cluster.go index 5d14717a15e7d..abb055cdfa354 100644 --- a/cmd/argocd/commands/admin/cluster.go +++ b/cmd/argocd/commands/admin/cluster.go @@ -25,7 +25,7 @@ import ( "github.com/argoproj/argo-cd/v2/common" "github.com/argoproj/argo-cd/v2/controller/sharding" argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient" - argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned" "github.com/argoproj/argo-cd/v2/util/argo" cacheutil "github.com/argoproj/argo-cd/v2/util/cache" @@ -71,14 +71,14 @@ argocd admin cluster namespaces my-cluster `, } type ClusterWithInfo struct { - argoappv1.Cluster + v1alpha1.Cluster // Shard holds controller shard number that handles the cluster Shard int // Namespaces holds list of namespaces managed by Argo CD in the cluster Namespaces []string } -func loadClusters(ctx context.Context, kubeClient *kubernetes.Clientset, appClient *versioned.Clientset, replicas int, namespace string, portForwardRedis bool, cacheSrc func() (*appstatecache.Cache, error), shard int, redisName string, redisHaProxyName string, redisCompressionStr string) ([]ClusterWithInfo, error) { +func loadClusters(ctx context.Context, kubeClient *kubernetes.Clientset, appClient *versioned.Clientset, replicas int, shardingAlgorithm string, namespace string, portForwardRedis bool, cacheSrc func() (*appstatecache.Cache, error), shard int, redisName string, redisHaProxyName string, redisCompressionStr string) ([]ClusterWithInfo, error) { settingsMgr := settings.NewSettingsManager(ctx, kubeClient, namespace) argoDB := db.NewDB(namespace, settingsMgr, kubeClient) @@ -86,6 +86,10 @@ func loadClusters(ctx context.Context, kubeClient *kubernetes.Clientset, appClie if err != nil { return nil, err } + clusterShardingCache := sharding.NewClusterSharding(argoDB, shard, replicas, shardingAlgorithm) + clusterShardingCache.Init(clustersList) + clusterShards := clusterShardingCache.GetDistribution() + var cache *appstatecache.Cache if portForwardRedis { overrides := clientcmd.ConfigOverrides{} @@ -122,8 +126,15 @@ func loadClusters(ctx context.Context, kubeClient *kubernetes.Clientset, appClie apps[i] = app } clusters := make([]ClusterWithInfo, len(clustersList.Items)) + batchSize := 10 batchesCount := int(math.Ceil(float64(len(clusters)) / float64(batchSize))) + clusterSharding := &sharding.ClusterSharding{ + Shard: shard, + Replicas: replicas, + Shards: make(map[string]int), + Clusters: make(map[string]*v1alpha1.Cluster), + } for batchNum := 0; batchNum < batchesCount; batchNum++ { batchStart := batchSize * batchNum batchEnd := batchSize * (batchNum + 1) @@ -135,12 +146,12 @@ func loadClusters(ctx context.Context, kubeClient *kubernetes.Clientset, appClie clusterShard := 0 cluster := batch[i] if replicas > 0 { - distributionFunction := sharding.GetDistributionFunction(argoDB, common.DefaultShardingAlgorithm) + distributionFunction := sharding.GetDistributionFunction(clusterSharding.GetClusterAccessor(), common.DefaultShardingAlgorithm, replicas) distributionFunction(&cluster) + clusterShard := clusterShards[cluster.Server] cluster.Shard = pointer.Int64(int64(clusterShard)) log.Infof("Cluster with uid: %s will be processed by shard %d", cluster.ID, clusterShard) } - if shard != -1 && clusterShard != shard { return nil } @@ -176,6 +187,7 @@ func NewClusterShardsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm var ( shard int replicas int + shardingAlgorithm string clientConfig clientcmd.ClientConfig cacheSrc func() (*appstatecache.Cache, error) portForwardRedis bool @@ -183,7 +195,7 @@ func NewClusterShardsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm ) var command = cobra.Command{ Use: "shards", - Short: "Print information about each controller shard and portion of Kubernetes resources it is responsible for.", + Short: "Print information about each controller shard and the estimated portion of Kubernetes resources it is responsible for.", Run: func(cmd *cobra.Command, args []string) { ctx := cmd.Context() @@ -203,8 +215,7 @@ func NewClusterShardsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm if replicas == 0 { return } - - clusters, err := loadClusters(ctx, kubeClient, appClient, replicas, namespace, portForwardRedis, cacheSrc, shard, clientOpts.RedisName, clientOpts.RedisHaProxyName, redisCompressionStr) + clusters, err := loadClusters(ctx, kubeClient, appClient, replicas, shardingAlgorithm, namespace, portForwardRedis, cacheSrc, shard, clientOpts.RedisName, clientOpts.RedisHaProxyName, redisCompressionStr) errors.CheckError(err) if len(clusters) == 0 { return @@ -216,7 +227,9 @@ func NewClusterShardsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm clientConfig = cli.AddKubectlFlagsToCmd(&command) command.Flags().IntVar(&shard, "shard", -1, "Cluster shard filter") command.Flags().IntVar(&replicas, "replicas", 0, "Application controller replicas count. Inferred from number of running controller pods if not specified") + command.Flags().StringVar(&shardingAlgorithm, "sharding-method", common.DefaultShardingAlgorithm, "Sharding method. Defaults: legacy. Supported sharding methods are : [legacy, round-robin] ") command.Flags().BoolVar(&portForwardRedis, "port-forward-redis", true, "Automatically port-forward ha proxy redis from current namespace?") + cacheSrc = appstatecache.AddCacheFlagsToCmd(&command) // parse all added flags so far to get the redis-compression flag that was added by AddCacheFlagsToCmd() above @@ -461,6 +474,7 @@ func NewClusterStatsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comma var ( shard int replicas int + shardingAlgorithm string clientConfig clientcmd.ClientConfig cacheSrc func() (*appstatecache.Cache, error) portForwardRedis bool @@ -494,7 +508,7 @@ argocd admin cluster stats target-cluster`, replicas, err = getControllerReplicas(ctx, kubeClient, namespace, clientOpts.AppControllerName) errors.CheckError(err) } - clusters, err := loadClusters(ctx, kubeClient, appClient, replicas, namespace, portForwardRedis, cacheSrc, shard, clientOpts.RedisName, clientOpts.RedisHaProxyName, redisCompressionStr) + clusters, err := loadClusters(ctx, kubeClient, appClient, replicas, shardingAlgorithm, namespace, portForwardRedis, cacheSrc, shard, clientOpts.RedisName, clientOpts.RedisHaProxyName, redisCompressionStr) errors.CheckError(err) w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) @@ -508,6 +522,7 @@ argocd admin cluster stats target-cluster`, clientConfig = cli.AddKubectlFlagsToCmd(&command) command.Flags().IntVar(&shard, "shard", -1, "Cluster shard filter") command.Flags().IntVar(&replicas, "replicas", 0, "Application controller replicas count. Inferred from number of running controller pods if not specified") + command.Flags().StringVar(&shardingAlgorithm, "sharding-method", common.DefaultShardingAlgorithm, "Sharding method. Defaults: legacy. Supported sharding methods are : [legacy, round-robin] ") command.Flags().BoolVar(&portForwardRedis, "port-forward-redis", true, "Automatically port-forward ha proxy redis from current namespace?") cacheSrc = appstatecache.AddCacheFlagsToCmd(&command) @@ -610,15 +625,16 @@ func NewGenClusterConfigCommand(pathOpts *clientcmd.PathOptions) *cobra.Command errors.CheckError(err) kubeClientset := fake.NewSimpleClientset() - var awsAuthConf *argoappv1.AWSAuthConfig - var execProviderConf *argoappv1.ExecProviderConfig + var awsAuthConf *v1alpha1.AWSAuthConfig + var execProviderConf *v1alpha1.ExecProviderConfig if clusterOpts.AwsClusterName != "" { - awsAuthConf = &argoappv1.AWSAuthConfig{ + awsAuthConf = &v1alpha1.AWSAuthConfig{ ClusterName: clusterOpts.AwsClusterName, RoleARN: clusterOpts.AwsRoleArn, + Profile: clusterOpts.AwsProfile, } } else if clusterOpts.ExecProviderCommand != "" { - execProviderConf = &argoappv1.ExecProviderConfig{ + execProviderConf = &v1alpha1.ExecProviderConfig{ Command: clusterOpts.ExecProviderCommand, Args: clusterOpts.ExecProviderArgs, Env: clusterOpts.ExecProviderEnv, @@ -642,7 +658,7 @@ func NewGenClusterConfigCommand(pathOpts *clientcmd.PathOptions) *cobra.Command clst := cmdutil.NewCluster(contextName, clusterOpts.Namespaces, clusterOpts.ClusterResources, conf, bearerToken, awsAuthConf, execProviderConf, labelsMap, annotationsMap) if clusterOpts.InClusterEndpoint() { - clst.Server = argoappv1.KubernetesInternalAPIServerAddr + clst.Server = v1alpha1.KubernetesInternalAPIServerAddr } if clusterOpts.ClusterEndpoint == string(cmdutil.KubePublicEndpoint) { // Ignore `kube-public` cluster endpoints, since this command is intended to run without invoking any network connections. diff --git a/cmd/argocd/commands/app.go b/cmd/argocd/commands/app.go index 0a54c517ca696..8e49fbc0e29e1 100644 --- a/cmd/argocd/commands/app.go +++ b/cmd/argocd/commands/app.go @@ -1624,7 +1624,7 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co list, err := appIf.List(ctx, &application.ApplicationQuery{Selector: pointer.String(selector)}) errors.CheckError(err) for _, i := range list.Items { - appNames = append(appNames, i.Name) + appNames = append(appNames, i.QualifiedName()) } } for _, appName := range appNames { @@ -1995,7 +1995,7 @@ func getAppNamesBySelector(ctx context.Context, appIf application.ApplicationSer return []string{}, fmt.Errorf("no apps match selector %v", selector) } for _, i := range list.Items { - appNames = append(appNames, i.Name) + appNames = append(appNames, i.QualifiedName()) } } return appNames, nil diff --git a/cmd/argocd/commands/cluster.go b/cmd/argocd/commands/cluster.go index 3df4be6632d85..f203b82ae9ac0 100644 --- a/cmd/argocd/commands/cluster.go +++ b/cmd/argocd/commands/cluster.go @@ -111,6 +111,7 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie awsAuthConf = &argoappv1.AWSAuthConfig{ ClusterName: clusterOpts.AwsClusterName, RoleARN: clusterOpts.AwsRoleArn, + Profile: clusterOpts.AwsProfile, } } else if clusterOpts.ExecProviderCommand != "" { execProviderConf = &argoappv1.ExecProviderConfig{ diff --git a/cmd/util/cluster.go b/cmd/util/cluster.go index 95c071c882b12..dffb52e775a97 100644 --- a/cmd/util/cluster.go +++ b/cmd/util/cluster.go @@ -144,6 +144,7 @@ type ClusterOptions struct { Upsert bool ServiceAccount string AwsRoleArn string + AwsProfile string AwsClusterName string SystemNamespace string Namespaces []string @@ -169,6 +170,7 @@ func AddClusterFlags(command *cobra.Command, opts *ClusterOptions) { command.Flags().BoolVar(&opts.InCluster, "in-cluster", false, "Indicates Argo CD resides inside this cluster and should connect using the internal k8s hostname (kubernetes.default.svc)") command.Flags().StringVar(&opts.AwsClusterName, "aws-cluster-name", "", "AWS Cluster name if set then aws cli eks token command will be used to access cluster") command.Flags().StringVar(&opts.AwsRoleArn, "aws-role-arn", "", "Optional AWS role arn. If set then AWS IAM Authenticator assumes a role to perform cluster operations instead of the default AWS credential provider chain.") + command.Flags().StringVar(&opts.AwsProfile, "aws-profile", "", "Optional AWS profile. If set then AWS IAM Authenticator uses this profile to perform cluster operations instead of the default AWS credential provider chain.") command.Flags().StringArrayVar(&opts.Namespaces, "namespace", nil, "List of namespaces which are allowed to manage") command.Flags().BoolVar(&opts.ClusterResources, "cluster-resources", false, "Indicates if cluster level resources should be managed. The setting is used only if list of managed namespaces is not empty.") command.Flags().StringVar(&opts.Name, "name", "", "Overwrite the cluster name") diff --git a/common/common.go b/common/common.go index c5b9362f7f943..2f053d7a28198 100644 --- a/common/common.go +++ b/common/common.go @@ -115,9 +115,9 @@ const ( LegacyShardingAlgorithm = "legacy" // RoundRobinShardingAlgorithm is a flag value that can be opted for Sharding Algorithm it uses an equal distribution accross all shards RoundRobinShardingAlgorithm = "round-robin" - DefaultShardingAlgorithm = LegacyShardingAlgorithm // AppControllerHeartbeatUpdateRetryCount is the retry count for updating the Shard Mapping to the Shard Mapping ConfigMap used by Application Controller AppControllerHeartbeatUpdateRetryCount = 3 + DefaultShardingAlgorithm = LegacyShardingAlgorithm ) // Dex related constants diff --git a/controller/appcontroller.go b/controller/appcontroller.go index 0ded95de65d15..e6dee507caa2e 100644 --- a/controller/appcontroller.go +++ b/controller/appcontroller.go @@ -6,6 +6,7 @@ import ( goerrors "errors" "fmt" "math" + "math/rand" "net/http" "reflect" "runtime/debug" @@ -47,7 +48,6 @@ import ( "github.com/argoproj/argo-cd/v2/controller/sharding" "github.com/argoproj/argo-cd/v2/pkg/apis/application" appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" - argov1alpha "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned" "github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions/application/v1alpha1" applisters "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1" @@ -118,6 +118,7 @@ type ApplicationController struct { stateCache statecache.LiveStateCache statusRefreshTimeout time.Duration statusHardRefreshTimeout time.Duration + statusRefreshJitter time.Duration selfHealTimeout time.Duration repoClientset apiclient.Clientset db db.ArgoDB @@ -126,7 +127,7 @@ type ApplicationController struct { refreshRequestedAppsMutex *sync.Mutex metricsServer *metrics.MetricsServer kubectlSemaphore *semaphore.Weighted - clusterFilter func(cluster *appv1.Cluster) bool + clusterSharding sharding.ClusterShardingCache projByNameCache sync.Map applicationNamespaces []string } @@ -142,6 +143,7 @@ func NewApplicationController( kubectl kube.Kubectl, appResyncPeriod time.Duration, appHardResyncPeriod time.Duration, + appResyncJitter time.Duration, selfHealTimeout time.Duration, repoErrorGracePeriod time.Duration, metricsPort int, @@ -149,12 +151,12 @@ func NewApplicationController( metricsApplicationLabels []string, kubectlParallelismLimit int64, persistResourceHealth bool, - clusterFilter func(cluster *appv1.Cluster) bool, + clusterSharding sharding.ClusterShardingCache, applicationNamespaces []string, rateLimiterConfig *ratelimiter.AppControllerRateLimiterConfig, serverSideDiff bool, ) (*ApplicationController, error) { - log.Infof("appResyncPeriod=%v, appHardResyncPeriod=%v", appResyncPeriod, appHardResyncPeriod) + log.Infof("appResyncPeriod=%v, appHardResyncPeriod=%v, appResyncJitter=%v", appResyncPeriod, appHardResyncPeriod, appResyncJitter) db := db.NewDB(namespace, settingsMgr, kubeClientset) if rateLimiterConfig == nil { rateLimiterConfig = ratelimiter.GetDefaultAppRateLimiterConfig() @@ -174,12 +176,13 @@ func NewApplicationController( db: db, statusRefreshTimeout: appResyncPeriod, statusHardRefreshTimeout: appHardResyncPeriod, + statusRefreshJitter: appResyncJitter, refreshRequestedApps: make(map[string]CompareWith), refreshRequestedAppsMutex: &sync.Mutex{}, auditLogger: argo.NewAuditLogger(namespace, kubeClientset, common.ApplicationController), settingsMgr: settingsMgr, selfHealTimeout: selfHealTimeout, - clusterFilter: clusterFilter, + clusterSharding: clusterSharding, projByNameCache: sync.Map{}, applicationNamespaces: applicationNamespaces, } @@ -260,7 +263,7 @@ func NewApplicationController( return nil, err } } - stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settingsMgr, kubectl, ctrl.metricsServer, ctrl.handleObjectUpdated, clusterFilter, argo.NewResourceTracking()) + stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settingsMgr, kubectl, ctrl.metricsServer, ctrl.handleObjectUpdated, clusterSharding, argo.NewResourceTracking()) appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectl, ctrl.settingsMgr, stateCache, projInformer, ctrl.metricsServer, argoCache, ctrl.statusRefreshTimeout, argo.NewResourceTracking(), persistResourceHealth, repoErrorGracePeriod, serverSideDiff) ctrl.appInformer = appInformer ctrl.appLister = appLister @@ -772,6 +775,13 @@ func (ctrl *ApplicationController) Run(ctx context.Context, statusProcessors int go ctrl.projInformer.Run(ctx.Done()) go ctrl.deploymentInformer.Informer().Run(ctx.Done()) + clusters, err := ctrl.db.ListClusters(ctx) + if err != nil { + log.Warnf("Cannot init sharding. Error while querying clusters list from database: %v", err) + } else { + ctrl.clusterSharding.Init(clusters) + } + errors.CheckError(ctrl.stateCache.Init()) if !cache.WaitForCacheSync(ctx.Done(), ctrl.appInformer.HasSynced, ctrl.projInformer.HasSynced) { @@ -1023,7 +1033,7 @@ func (ctrl *ApplicationController) getPermittedAppLiveObjects(app *appv1.Applica return objsMap, nil } -func (ctrl *ApplicationController) isValidDestination(app *appv1.Application) (bool, *argov1alpha.Cluster) { +func (ctrl *ApplicationController) isValidDestination(app *appv1.Application) (bool, *appv1.Cluster) { // Validate the cluster using the Application destination's `name` field, if applicable, // and set the Server field, if needed. if err := argo.ValidateDestination(context.Background(), &app.Spec.Destination, ctrl.db); err != nil { @@ -1636,6 +1646,7 @@ func (ctrl *ApplicationController) needRefreshAppStatus(app *appv1.Application, var reason string compareWith := CompareWithLatest refreshType := appv1.RefreshTypeNormal + softExpired := app.Status.ReconciledAt == nil || app.Status.ReconciledAt.Add(statusRefreshTimeout).Before(time.Now().UTC()) hardExpired := (app.Status.ReconciledAt == nil || app.Status.ReconciledAt.Add(statusHardRefreshTimeout).Before(time.Now().UTC())) && statusHardRefreshTimeout.Seconds() != 0 @@ -1976,15 +1987,11 @@ func (ctrl *ApplicationController) canProcessApp(obj interface{}) bool { } } - if ctrl.clusterFilter != nil { - cluster, err := ctrl.db.GetCluster(context.Background(), app.Spec.Destination.Server) - if err != nil { - return ctrl.clusterFilter(nil) - } - return ctrl.clusterFilter(cluster) + cluster, err := ctrl.db.GetCluster(context.Background(), app.Spec.Destination.Server) + if err != nil { + return ctrl.clusterSharding.IsManagedCluster(nil) } - - return true + return ctrl.clusterSharding.IsManagedCluster(cluster) } func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.SharedIndexInformer, applisters.ApplicationLister) { @@ -2092,14 +2099,25 @@ func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.Shar if err != nil { return } + var compareWith *CompareWith + var delay *time.Duration + oldApp, oldOK := old.(*appv1.Application) newApp, newOK := new.(*appv1.Application) - if oldOK && newOK && automatedSyncEnabled(oldApp, newApp) { - log.WithField("application", newApp.QualifiedName()).Info("Enabled automated sync") - compareWith = CompareWithLatest.Pointer() + if oldOK && newOK { + if automatedSyncEnabled(oldApp, newApp) { + log.WithField("application", newApp.QualifiedName()).Info("Enabled automated sync") + compareWith = CompareWithLatest.Pointer() + } + if ctrl.statusRefreshJitter != 0 && oldApp.ResourceVersion == newApp.ResourceVersion { + // Handler is refreshing the apps, add a random jitter to spread the load and avoid spikes + jitter := time.Duration(float64(ctrl.statusRefreshJitter) * rand.Float64()) + delay = &jitter + } } - ctrl.requestAppRefresh(newApp.QualifiedName(), compareWith, nil) + + ctrl.requestAppRefresh(newApp.QualifiedName(), compareWith, delay) ctrl.appOperationQueue.AddRateLimited(key) }, DeleteFunc: func(obj interface{}) { @@ -2136,7 +2154,7 @@ func (ctrl *ApplicationController) projectErrorToCondition(err error, app *appv1 } func (ctrl *ApplicationController) RegisterClusterSecretUpdater(ctx context.Context) { - updater := NewClusterInfoUpdater(ctrl.stateCache, ctrl.db, ctrl.appLister.Applications(""), ctrl.cache, ctrl.clusterFilter, ctrl.getAppProj, ctrl.namespace) + updater := NewClusterInfoUpdater(ctrl.stateCache, ctrl.db, ctrl.appLister.Applications(""), ctrl.cache, ctrl.clusterSharding.IsManagedCluster, ctrl.getAppProj, ctrl.namespace) go updater.Run(ctx) } @@ -2188,4 +2206,4 @@ func (ctrl *ApplicationController) toAppQualifiedName(appName, appNamespace stri return fmt.Sprintf("%s/%s", appNamespace, appName) } -type ClusterFilterFunction func(c *argov1alpha.Cluster, distributionFunction sharding.DistributionFunction) bool +type ClusterFilterFunction func(c *appv1.Cluster, distributionFunction sharding.DistributionFunction) bool diff --git a/controller/appcontroller_test.go b/controller/appcontroller_test.go index bf3d8bb3a2e4c..4162a9983e941 100644 --- a/controller/appcontroller_test.go +++ b/controller/appcontroller_test.go @@ -17,7 +17,9 @@ import ( "github.com/argoproj/argo-cd/v2/common" statecache "github.com/argoproj/argo-cd/v2/controller/cache" + "github.com/argoproj/argo-cd/v2/controller/sharding" + dbmocks "github.com/argoproj/argo-cd/v2/util/db/mocks" "github.com/argoproj/gitops-engine/pkg/cache/mocks" synccommon "github.com/argoproj/gitops-engine/pkg/sync/common" "github.com/argoproj/gitops-engine/pkg/utils/kube" @@ -142,6 +144,7 @@ func newFakeController(data *fakeData, repoErr error) *ApplicationController { kubectl, time.Minute, time.Hour, + time.Second, time.Minute, time.Second*10, common.DefaultPortArgoCDMetrics, @@ -152,8 +155,13 @@ func newFakeController(data *fakeData, repoErr error) *ApplicationController { nil, data.applicationNamespaces, nil, + false, ) + db := &dbmocks.ArgoDB{} + db.On("GetApplicationControllerReplicas").Return(1) + // Setting a default sharding algorithm for the tests where we cannot set it. + ctrl.clusterSharding = sharding.NewClusterSharding(db, 0, 1, common.DefaultShardingAlgorithm) if err != nil { panic(err) } @@ -686,7 +694,6 @@ func TestFinalizeAppDeletion(t *testing.T) { ctrl := newFakeController(&fakeData{apps: []runtime.Object{app, &defaultProj}, managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{ kube.GetResourceKey(appObj): appObj, }}, nil) - patched := false fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset) defaultReactor := fakeAppCs.ReactionChain[0] @@ -1809,13 +1816,11 @@ func Test_canProcessApp(t *testing.T) { }) t.Run("with cluster filter, good namespace", func(t *testing.T) { app.Namespace = "good" - ctrl.clusterFilter = func(_ *v1alpha1.Cluster) bool { return true } canProcess := ctrl.canProcessApp(app) assert.True(t, canProcess) }) t.Run("with cluster filter, bad namespace", func(t *testing.T) { app.Namespace = "bad" - ctrl.clusterFilter = func(_ *v1alpha1.Cluster) bool { return true } canProcess := ctrl.canProcessApp(app) assert.False(t, canProcess) }) diff --git a/controller/cache/cache.go b/controller/cache/cache.go index 9eac161714089..e3b1d7b77f19d 100644 --- a/controller/cache/cache.go +++ b/controller/cache/cache.go @@ -29,6 +29,7 @@ import ( "k8s.io/client-go/tools/cache" "github.com/argoproj/argo-cd/v2/controller/metrics" + "github.com/argoproj/argo-cd/v2/controller/sharding" "github.com/argoproj/argo-cd/v2/pkg/apis/application" appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v2/util/argo" @@ -168,7 +169,7 @@ func NewLiveStateCache( kubectl kube.Kubectl, metricsServer *metrics.MetricsServer, onObjectUpdated ObjectUpdatedHandler, - clusterFilter func(cluster *appv1.Cluster) bool, + clusterSharding sharding.ClusterShardingCache, resourceTracking argo.ResourceTracking) LiveStateCache { return &liveStateCache{ @@ -179,7 +180,7 @@ func NewLiveStateCache( kubectl: kubectl, settingsMgr: settingsMgr, metricsServer: metricsServer, - clusterFilter: clusterFilter, + clusterSharding: clusterSharding, resourceTracking: resourceTracking, } } @@ -202,7 +203,7 @@ type liveStateCache struct { kubectl kube.Kubectl settingsMgr *settings.SettingsManager metricsServer *metrics.MetricsServer - clusterFilter func(cluster *appv1.Cluster) bool + clusterSharding sharding.ClusterShardingCache resourceTracking argo.ResourceTracking clusters map[string]clustercache.ClusterCache @@ -722,22 +723,24 @@ func (c *liveStateCache) Run(ctx context.Context) error { } func (c *liveStateCache) canHandleCluster(cluster *appv1.Cluster) bool { - if c.clusterFilter == nil { - return true - } - return c.clusterFilter(cluster) + return c.clusterSharding.IsManagedCluster(cluster) } func (c *liveStateCache) handleAddEvent(cluster *appv1.Cluster) { + c.clusterSharding.Add(cluster) if !c.canHandleCluster(cluster) { log.Infof("Ignoring cluster %s", cluster.Server) return } - c.lock.Lock() _, ok := c.clusters[cluster.Server] c.lock.Unlock() if !ok { + log.Debugf("Checking if cache %v / cluster %v has appInformer %v", c, cluster, c.appInformer) + if c.appInformer == nil { + log.Warn("Cannot get a cluster appInformer. Cache may not be started this time") + return + } if c.isClusterHasApps(c.appInformer.GetStore().List(), cluster) { go func() { // warm up cache for cluster with apps @@ -748,6 +751,7 @@ func (c *liveStateCache) handleAddEvent(cluster *appv1.Cluster) { } func (c *liveStateCache) handleModEvent(oldCluster *appv1.Cluster, newCluster *appv1.Cluster) { + c.clusterSharding.Update(newCluster) c.lock.Lock() cluster, ok := c.clusters[newCluster.Server] c.lock.Unlock() @@ -790,6 +794,7 @@ func (c *liveStateCache) handleModEvent(oldCluster *appv1.Cluster, newCluster *a func (c *liveStateCache) handleDeleteEvent(clusterServer string) { c.lock.RLock() + c.clusterSharding.Delete(clusterServer) cluster, ok := c.clusters[clusterServer] c.lock.RUnlock() if ok { diff --git a/controller/cache/cache_test.go b/controller/cache/cache_test.go index c94038a89b881..53a03ca81995e 100644 --- a/controller/cache/cache_test.go +++ b/controller/cache/cache_test.go @@ -21,7 +21,11 @@ import ( "github.com/stretchr/testify/mock" "k8s.io/client-go/kubernetes/fake" + "github.com/argoproj/argo-cd/v2/common" + "github.com/argoproj/argo-cd/v2/controller/metrics" + "github.com/argoproj/argo-cd/v2/controller/sharding" appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + dbmocks "github.com/argoproj/argo-cd/v2/util/db/mocks" argosettings "github.com/argoproj/argo-cd/v2/util/settings" ) @@ -35,11 +39,13 @@ func TestHandleModEvent_HasChanges(t *testing.T) { clusterCache := &mocks.ClusterCache{} clusterCache.On("Invalidate", mock.Anything, mock.Anything).Return(nil).Once() clusterCache.On("EnsureSynced").Return(nil).Once() - + db := &dbmocks.ArgoDB{} + db.On("GetApplicationControllerReplicas").Return(1) clustersCache := liveStateCache{ clusters: map[string]cache.ClusterCache{ "https://mycluster": clusterCache, }, + clusterSharding: sharding.NewClusterSharding(db, 0, 1, common.DefaultShardingAlgorithm), } clustersCache.handleModEvent(&appv1.Cluster{ @@ -56,14 +62,22 @@ func TestHandleModEvent_ClusterExcluded(t *testing.T) { clusterCache := &mocks.ClusterCache{} clusterCache.On("Invalidate", mock.Anything, mock.Anything).Return(nil).Once() clusterCache.On("EnsureSynced").Return(nil).Once() - + db := &dbmocks.ArgoDB{} + db.On("GetApplicationControllerReplicas").Return(1) clustersCache := liveStateCache{ - clusters: map[string]cache.ClusterCache{ - "https://mycluster": clusterCache, - }, - clusterFilter: func(cluster *appv1.Cluster) bool { - return false + db: nil, + appInformer: nil, + onObjectUpdated: func(managedByApp map[string]bool, ref v1.ObjectReference) { }, + kubectl: nil, + settingsMgr: &argosettings.SettingsManager{}, + metricsServer: &metrics.MetricsServer{}, + // returns a shard that never process any cluster + clusterSharding: sharding.NewClusterSharding(db, 0, 1, common.DefaultShardingAlgorithm), + resourceTracking: nil, + clusters: map[string]cache.ClusterCache{"https://mycluster": clusterCache}, + cacheSettings: cacheSettings{}, + lock: sync.RWMutex{}, } clustersCache.handleModEvent(&appv1.Cluster{ @@ -75,18 +89,20 @@ func TestHandleModEvent_ClusterExcluded(t *testing.T) { Namespaces: []string{"default"}, }) - assert.Len(t, clustersCache.clusters, 0) + assert.Len(t, clustersCache.clusters, 1) } func TestHandleModEvent_NoChanges(t *testing.T) { clusterCache := &mocks.ClusterCache{} clusterCache.On("Invalidate", mock.Anything).Panic("should not invalidate") clusterCache.On("EnsureSynced").Return(nil).Panic("should not re-sync") - + db := &dbmocks.ArgoDB{} + db.On("GetApplicationControllerReplicas").Return(1) clustersCache := liveStateCache{ clusters: map[string]cache.ClusterCache{ "https://mycluster": clusterCache, }, + clusterSharding: sharding.NewClusterSharding(db, 0, 1, common.DefaultShardingAlgorithm), } clustersCache.handleModEvent(&appv1.Cluster{ @@ -99,11 +115,11 @@ func TestHandleModEvent_NoChanges(t *testing.T) { } func TestHandleAddEvent_ClusterExcluded(t *testing.T) { + db := &dbmocks.ArgoDB{} + db.On("GetApplicationControllerReplicas").Return(1) clustersCache := liveStateCache{ - clusters: map[string]cache.ClusterCache{}, - clusterFilter: func(cluster *appv1.Cluster) bool { - return false - }, + clusters: map[string]cache.ClusterCache{}, + clusterSharding: sharding.NewClusterSharding(db, 0, 2, common.DefaultShardingAlgorithm), } clustersCache.handleAddEvent(&appv1.Cluster{ Server: "https://mycluster", @@ -118,6 +134,8 @@ func TestHandleDeleteEvent_CacheDeadlock(t *testing.T) { Server: "https://mycluster", Config: appv1.ClusterConfig{Username: "bar"}, } + db := &dbmocks.ArgoDB{} + db.On("GetApplicationControllerReplicas").Return(1) fakeClient := fake.NewSimpleClientset() settingsMgr := argosettings.NewSettingsManager(context.TODO(), fakeClient, "argocd") liveStateCacheLock := sync.RWMutex{} @@ -126,10 +144,8 @@ func TestHandleDeleteEvent_CacheDeadlock(t *testing.T) { clusters: map[string]cache.ClusterCache{ testCluster.Server: gitopsEngineClusterCache, }, - clusterFilter: func(cluster *appv1.Cluster) bool { - return true - }, - settingsMgr: settingsMgr, + clusterSharding: sharding.NewClusterSharding(db, 0, 1, common.DefaultShardingAlgorithm), + settingsMgr: settingsMgr, // Set the lock here so we can reference it later // nolint We need to overwrite here to have access to the lock lock: liveStateCacheLock, diff --git a/controller/sharding/cache.go b/controller/sharding/cache.go new file mode 100644 index 0000000000000..d16574accdf8a --- /dev/null +++ b/controller/sharding/cache.go @@ -0,0 +1,163 @@ +package sharding + +import ( + "sync" + + "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/argoproj/argo-cd/v2/util/db" + log "github.com/sirupsen/logrus" +) + +type ClusterShardingCache interface { + Init(clusters *v1alpha1.ClusterList) + Add(c *v1alpha1.Cluster) + Delete(clusterServer string) + Update(c *v1alpha1.Cluster) + IsManagedCluster(c *v1alpha1.Cluster) bool + GetDistribution() map[string]int +} + +type ClusterSharding struct { + Shard int + Replicas int + Shards map[string]int + Clusters map[string]*v1alpha1.Cluster + lock sync.RWMutex + getClusterShard DistributionFunction +} + +func NewClusterSharding(db db.ArgoDB, shard, replicas int, shardingAlgorithm string) ClusterShardingCache { + log.Debugf("Processing clusters from shard %d: Using filter function: %s", shard, shardingAlgorithm) + clusterSharding := &ClusterSharding{ + Shard: shard, + Replicas: replicas, + Shards: make(map[string]int), + Clusters: make(map[string]*v1alpha1.Cluster), + } + distributionFunction := NoShardingDistributionFunction() + if replicas > 1 { + log.Debugf("Processing clusters from shard %d: Using filter function: %s", shard, shardingAlgorithm) + distributionFunction = GetDistributionFunction(clusterSharding.GetClusterAccessor(), shardingAlgorithm, replicas) + } else { + log.Info("Processing all cluster shards") + } + clusterSharding.getClusterShard = distributionFunction + return clusterSharding +} + +// IsManagedCluster returns wheter or not the cluster should be processed by a given shard. +func (s *ClusterSharding) IsManagedCluster(c *v1alpha1.Cluster) bool { + s.lock.RLock() + defer s.lock.RUnlock() + if c == nil { // nil cluster (in-cluster) is always managed by current clusterShard + return true + } + clusterShard := 0 + if shard, ok := s.Shards[c.Server]; ok { + clusterShard = shard + } else { + log.Warnf("The cluster %s has no assigned shard.", c.Server) + } + log.Debugf("Checking if cluster %s with clusterShard %d should be processed by shard %d", c.Server, clusterShard, s.Shard) + return clusterShard == s.Shard +} + +func (sharding *ClusterSharding) Init(clusters *v1alpha1.ClusterList) { + sharding.lock.Lock() + defer sharding.lock.Unlock() + newClusters := make(map[string]*v1alpha1.Cluster, len(clusters.Items)) + for _, c := range clusters.Items { + newClusters[c.Server] = &c + } + sharding.Clusters = newClusters + sharding.updateDistribution() +} + +func (sharding *ClusterSharding) Add(c *v1alpha1.Cluster) { + sharding.lock.Lock() + defer sharding.lock.Unlock() + + old, ok := sharding.Clusters[c.Server] + sharding.Clusters[c.Server] = c + if !ok || hasShardingUpdates(old, c) { + sharding.updateDistribution() + } else { + log.Debugf("Skipping sharding distribution update. Cluster already added") + } +} + +func (sharding *ClusterSharding) Delete(clusterServer string) { + sharding.lock.Lock() + defer sharding.lock.Unlock() + if _, ok := sharding.Clusters[clusterServer]; ok { + delete(sharding.Clusters, clusterServer) + delete(sharding.Shards, clusterServer) + sharding.updateDistribution() + } +} + +func (sharding *ClusterSharding) Update(c *v1alpha1.Cluster) { + sharding.lock.Lock() + defer sharding.lock.Unlock() + + old, ok := sharding.Clusters[c.Server] + sharding.Clusters[c.Server] = c + if !ok || hasShardingUpdates(old, c) { + sharding.updateDistribution() + } else { + log.Debugf("Skipping sharding distribution update. No relevant changes") + } +} + +func (sharding *ClusterSharding) GetDistribution() map[string]int { + sharding.lock.RLock() + shards := sharding.Shards + sharding.lock.RUnlock() + + distribution := make(map[string]int, len(shards)) + for k, v := range shards { + distribution[k] = v + } + return distribution +} + +func (sharding *ClusterSharding) updateDistribution() { + log.Info("Updating cluster shards") + + for _, c := range sharding.Clusters { + shard := 0 + if c.Shard != nil { + requestedShard := int(*c.Shard) + if requestedShard < sharding.Replicas { + shard = requestedShard + } else { + log.Warnf("Specified cluster shard (%d) for cluster: %s is greater than the number of available shard (%d). Using shard 0.", requestedShard, c.Server, sharding.Replicas) + } + } else { + shard = sharding.getClusterShard(c) + } + var shard64 int64 = int64(shard) + c.Shard = &shard64 + sharding.Shards[c.Server] = shard + } +} + +// hasShardingUpdates returns true if the sharding distribution has been updated. +// nil checking is done for the corner case of the in-cluster cluster which may +// have a nil shard assigned +func hasShardingUpdates(old, new *v1alpha1.Cluster) bool { + if old == nil || new == nil || (old.Shard == nil && new.Shard == nil) { + return false + } + return old.Shard != new.Shard +} + +func (d *ClusterSharding) GetClusterAccessor() clusterAccessor { + return func() []*v1alpha1.Cluster { + clusters := make([]*v1alpha1.Cluster, 0, len(d.Clusters)) + for _, c := range d.Clusters { + clusters = append(clusters, c) + } + return clusters + } +} diff --git a/controller/sharding/sharding.go b/controller/sharding/sharding.go index 526896531dbca..2b86ed3f82bc6 100644 --- a/controller/sharding/sharding.go +++ b/controller/sharding/sharding.go @@ -40,6 +40,7 @@ const ShardControllerMappingKey = "shardControllerMapping" type DistributionFunction func(c *v1alpha1.Cluster) int type ClusterFilterFunction func(c *v1alpha1.Cluster) bool +type clusterAccessor func() []*v1alpha1.Cluster // shardApplicationControllerMapping stores the mapping of Shard Number to Application Controller in ConfigMap. // It also stores the heartbeat of last synced time of the application controller. @@ -53,8 +54,7 @@ type shardApplicationControllerMapping struct { // and returns wheter or not the cluster should be processed by a given shard. It calls the distributionFunction // to determine which shard will process the cluster, and if the given shard is equal to the calculated shard // the function will return true. -func GetClusterFilter(db db.ArgoDB, distributionFunction DistributionFunction, shard int) ClusterFilterFunction { - replicas := db.GetApplicationControllerReplicas() +func GetClusterFilter(db db.ArgoDB, distributionFunction DistributionFunction, replicas, shard int) ClusterFilterFunction { return func(c *v1alpha1.Cluster) bool { clusterShard := 0 if c != nil && c.Shard != nil { @@ -73,14 +73,14 @@ func GetClusterFilter(db db.ArgoDB, distributionFunction DistributionFunction, s // GetDistributionFunction returns which DistributionFunction should be used based on the passed algorithm and // the current datas. -func GetDistributionFunction(db db.ArgoDB, shardingAlgorithm string) DistributionFunction { - log.Infof("Using filter function: %s", shardingAlgorithm) - distributionFunction := LegacyDistributionFunction(db) +func GetDistributionFunction(clusters clusterAccessor, shardingAlgorithm string, replicasCount int) DistributionFunction { + log.Debugf("Using filter function: %s", shardingAlgorithm) + distributionFunction := LegacyDistributionFunction(replicasCount) switch shardingAlgorithm { case common.RoundRobinShardingAlgorithm: - distributionFunction = RoundRobinDistributionFunction(db) + distributionFunction = RoundRobinDistributionFunction(clusters, replicasCount) case common.LegacyShardingAlgorithm: - distributionFunction = LegacyDistributionFunction(db) + distributionFunction = LegacyDistributionFunction(replicasCount) default: log.Warnf("distribution type %s is not supported, defaulting to %s", shardingAlgorithm, common.DefaultShardingAlgorithm) } @@ -92,15 +92,21 @@ func GetDistributionFunction(db db.ArgoDB, shardingAlgorithm string) Distributio // is lightweight and can be distributed easily, however, it does not ensure an homogenous distribution as // some shards may get assigned more clusters than others. It is the legacy function distribution that is // kept for compatibility reasons -func LegacyDistributionFunction(db db.ArgoDB) DistributionFunction { - replicas := db.GetApplicationControllerReplicas() +func LegacyDistributionFunction(replicas int) DistributionFunction { return func(c *v1alpha1.Cluster) int { if replicas == 0 { + log.Debugf("Replicas count is : %d, returning -1", replicas) return -1 } if c == nil { + log.Debug("In-cluster: returning 0") return 0 } + // if Shard is manually set and the assigned value is lower than the number of replicas, + // then its value is returned otherwise it is the default calculated value + if c.Shard != nil && int(*c.Shard) < replicas { + return int(*c.Shard) + } id := c.ID log.Debugf("Calculating cluster shard for cluster id: %s", id) if id == "" { @@ -121,14 +127,19 @@ func LegacyDistributionFunction(db db.ArgoDB) DistributionFunction { // This function ensures an homogenous distribution: each shards got assigned the same number of // clusters +/-1 , but with the drawback of a reshuffling of clusters accross shards in case of some changes // in the cluster list -func RoundRobinDistributionFunction(db db.ArgoDB) DistributionFunction { - replicas := db.GetApplicationControllerReplicas() + +func RoundRobinDistributionFunction(clusters clusterAccessor, replicas int) DistributionFunction { return func(c *v1alpha1.Cluster) int { if replicas > 0 { if c == nil { // in-cluster does not necessarly have a secret assigned. So we are receiving a nil cluster here. return 0 + } + // if Shard is manually set and the assigned value is lower than the number of replicas, + // then its value is returned otherwise it is the default calculated value + if c.Shard != nil && int(*c.Shard) < replicas { + return int(*c.Shard) } else { - clusterIndexdByClusterIdMap := createClusterIndexByClusterIdMap(db) + clusterIndexdByClusterIdMap := createClusterIndexByClusterIdMap(clusters) clusterIndex, ok := clusterIndexdByClusterIdMap[c.ID] if !ok { log.Warnf("Cluster with id=%s not found in cluster map.", c.ID) @@ -144,6 +155,12 @@ func RoundRobinDistributionFunction(db db.ArgoDB) DistributionFunction { } } +// NoShardingDistributionFunction returns a DistributionFunction that will process all cluster by shard 0 +// the function is created for API compatibility purposes and is not supposed to be activated. +func NoShardingDistributionFunction() DistributionFunction { + return func(c *v1alpha1.Cluster) int { return 0 } +} + // InferShard extracts the shard index based on its hostname. func InferShard() (int, error) { hostname, err := osHostnameFunction() @@ -152,33 +169,29 @@ func InferShard() (int, error) { } parts := strings.Split(hostname, "-") if len(parts) == 0 { - return 0, fmt.Errorf("hostname should ends with shard number separated by '-' but got: %s", hostname) + log.Warnf("hostname should end with shard number separated by '-' but got: %s", hostname) + return 0, nil } shard, err := strconv.Atoi(parts[len(parts)-1]) if err != nil { - return 0, fmt.Errorf("hostname should ends with shard number separated by '-' but got: %s", hostname) + log.Warnf("hostname should end with shard number separated by '-' but got: %s", hostname) + return 0, nil } return int(shard), nil } -func getSortedClustersList(db db.ArgoDB) []v1alpha1.Cluster { - ctx := context.Background() - clustersList, dbErr := db.ListClusters(ctx) - if dbErr != nil { - log.Warnf("Error while querying clusters list from database: %v", dbErr) - return []v1alpha1.Cluster{} - } - clusters := clustersList.Items +func getSortedClustersList(getCluster clusterAccessor) []*v1alpha1.Cluster { + clusters := getCluster() sort.Slice(clusters, func(i, j int) bool { return clusters[i].ID < clusters[j].ID }) return clusters } -func createClusterIndexByClusterIdMap(db db.ArgoDB) map[string]int { - clusters := getSortedClustersList(db) +func createClusterIndexByClusterIdMap(getCluster clusterAccessor) map[string]int { + clusters := getSortedClustersList(getCluster) log.Debugf("ClustersList has %d items", len(clusters)) - clusterById := make(map[string]v1alpha1.Cluster) + clusterById := make(map[string]*v1alpha1.Cluster) clusterIndexedByClusterId := make(map[string]int) for i, cluster := range clusters { log.Debugf("Adding cluster with id=%s and name=%s to cluster's map", cluster.ID, cluster.Name) @@ -194,7 +207,6 @@ func createClusterIndexByClusterIdMap(db db.ArgoDB) map[string]int { // If the shard value passed to this function is -1, that is, the shard was not set as an environment variable, // we default the shard number to 0 for computing the default config map. func GetOrUpdateShardFromConfigMap(kubeClient *kubernetes.Clientset, settingsMgr *settings.SettingsManager, replicas, shard int) (int, error) { - hostname, err := osHostnameFunction() if err != nil { return -1, err diff --git a/controller/sharding/sharding_test.go b/controller/sharding/sharding_test.go index a8a25e11c4978..0992f7a9dfd7f 100644 --- a/controller/sharding/sharding_test.go +++ b/controller/sharding/sharding_test.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "strconv" "testing" "time" @@ -19,18 +20,20 @@ import ( func TestGetShardByID_NotEmptyID(t *testing.T) { db := &dbmocks.ArgoDB{} - db.On("GetApplicationControllerReplicas").Return(1) - assert.Equal(t, 0, LegacyDistributionFunction(db)(&v1alpha1.Cluster{ID: "1"})) - assert.Equal(t, 0, LegacyDistributionFunction(db)(&v1alpha1.Cluster{ID: "2"})) - assert.Equal(t, 0, LegacyDistributionFunction(db)(&v1alpha1.Cluster{ID: "3"})) - assert.Equal(t, 0, LegacyDistributionFunction(db)(&v1alpha1.Cluster{ID: "4"})) + replicasCount := 1 + db.On("GetApplicationControllerReplicas").Return(replicasCount) + assert.Equal(t, 0, LegacyDistributionFunction(replicasCount)(&v1alpha1.Cluster{ID: "1"})) + assert.Equal(t, 0, LegacyDistributionFunction(replicasCount)(&v1alpha1.Cluster{ID: "2"})) + assert.Equal(t, 0, LegacyDistributionFunction(replicasCount)(&v1alpha1.Cluster{ID: "3"})) + assert.Equal(t, 0, LegacyDistributionFunction(replicasCount)(&v1alpha1.Cluster{ID: "4"})) } func TestGetShardByID_EmptyID(t *testing.T) { db := &dbmocks.ArgoDB{} - db.On("GetApplicationControllerReplicas").Return(1) + replicasCount := 1 + db.On("GetApplicationControllerReplicas").Return(replicasCount) distributionFunction := LegacyDistributionFunction - shard := distributionFunction(db)(&v1alpha1.Cluster{}) + shard := distributionFunction(replicasCount)(&v1alpha1.Cluster{}) assert.Equal(t, 0, shard) } @@ -38,7 +41,7 @@ func TestGetShardByID_NoReplicas(t *testing.T) { db := &dbmocks.ArgoDB{} db.On("GetApplicationControllerReplicas").Return(0) distributionFunction := LegacyDistributionFunction - shard := distributionFunction(db)(&v1alpha1.Cluster{}) + shard := distributionFunction(0)(&v1alpha1.Cluster{}) assert.Equal(t, -1, shard) } @@ -46,16 +49,16 @@ func TestGetShardByID_NoReplicasUsingHashDistributionFunction(t *testing.T) { db := &dbmocks.ArgoDB{} db.On("GetApplicationControllerReplicas").Return(0) distributionFunction := LegacyDistributionFunction - shard := distributionFunction(db)(&v1alpha1.Cluster{}) + shard := distributionFunction(0)(&v1alpha1.Cluster{}) assert.Equal(t, -1, shard) } func TestGetShardByID_NoReplicasUsingHashDistributionFunctionWithClusters(t *testing.T) { - db, cluster1, cluster2, cluster3, cluster4, cluster5 := createTestClusters() + clusters, db, cluster1, cluster2, cluster3, cluster4, cluster5 := createTestClusters() // Test with replicas set to 0 db.On("GetApplicationControllerReplicas").Return(0) t.Setenv(common.EnvControllerShardingAlgorithm, common.RoundRobinShardingAlgorithm) - distributionFunction := RoundRobinDistributionFunction(db) + distributionFunction := RoundRobinDistributionFunction(clusters, 0) assert.Equal(t, -1, distributionFunction(nil)) assert.Equal(t, -1, distributionFunction(&cluster1)) assert.Equal(t, -1, distributionFunction(&cluster2)) @@ -65,137 +68,112 @@ func TestGetShardByID_NoReplicasUsingHashDistributionFunctionWithClusters(t *tes } func TestGetClusterFilterDefault(t *testing.T) { - shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...) + //shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...) + clusterAccessor, _, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters() os.Unsetenv(common.EnvControllerShardingAlgorithm) - db := &dbmocks.ArgoDB{} - db.On("GetApplicationControllerReplicas").Return(2) - filter := GetClusterFilter(db, GetDistributionFunction(db, common.DefaultShardingAlgorithm), shardIndex) - assert.False(t, filter(&v1alpha1.Cluster{ID: "1"})) - assert.True(t, filter(&v1alpha1.Cluster{ID: "2"})) - assert.False(t, filter(&v1alpha1.Cluster{ID: "3"})) - assert.True(t, filter(&v1alpha1.Cluster{ID: "4"})) + replicasCount := 2 + distributionFunction := RoundRobinDistributionFunction(clusterAccessor, replicasCount) + assert.Equal(t, 0, distributionFunction(nil)) + assert.Equal(t, 0, distributionFunction(&cluster1)) + assert.Equal(t, 1, distributionFunction(&cluster2)) + assert.Equal(t, 0, distributionFunction(&cluster3)) + assert.Equal(t, 1, distributionFunction(&cluster4)) } func TestGetClusterFilterLegacy(t *testing.T) { - shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...) - db := &dbmocks.ArgoDB{} - db.On("GetApplicationControllerReplicas").Return(2) + //shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...) + clusterAccessor, db, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters() + replicasCount := 2 + db.On("GetApplicationControllerReplicas").Return(replicasCount) t.Setenv(common.EnvControllerShardingAlgorithm, common.LegacyShardingAlgorithm) - filter := GetClusterFilter(db, GetDistributionFunction(db, common.LegacyShardingAlgorithm), shardIndex) - assert.False(t, filter(&v1alpha1.Cluster{ID: "1"})) - assert.True(t, filter(&v1alpha1.Cluster{ID: "2"})) - assert.False(t, filter(&v1alpha1.Cluster{ID: "3"})) - assert.True(t, filter(&v1alpha1.Cluster{ID: "4"})) + distributionFunction := RoundRobinDistributionFunction(clusterAccessor, replicasCount) + assert.Equal(t, 0, distributionFunction(nil)) + assert.Equal(t, 0, distributionFunction(&cluster1)) + assert.Equal(t, 1, distributionFunction(&cluster2)) + assert.Equal(t, 0, distributionFunction(&cluster3)) + assert.Equal(t, 1, distributionFunction(&cluster4)) } func TestGetClusterFilterUnknown(t *testing.T) { - shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...) - db := &dbmocks.ArgoDB{} - db.On("GetApplicationControllerReplicas").Return(2) + clusterAccessor, db, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters() + // Test with replicas set to 0 + t.Setenv(common.EnvControllerReplicas, "2") + os.Unsetenv(common.EnvControllerShardingAlgorithm) t.Setenv(common.EnvControllerShardingAlgorithm, "unknown") - filter := GetClusterFilter(db, GetDistributionFunction(db, "unknown"), shardIndex) - assert.False(t, filter(&v1alpha1.Cluster{ID: "1"})) - assert.True(t, filter(&v1alpha1.Cluster{ID: "2"})) - assert.False(t, filter(&v1alpha1.Cluster{ID: "3"})) - assert.True(t, filter(&v1alpha1.Cluster{ID: "4"})) + replicasCount := 2 + db.On("GetApplicationControllerReplicas").Return(replicasCount) + distributionFunction := GetDistributionFunction(clusterAccessor, "unknown", replicasCount) + assert.Equal(t, 0, distributionFunction(nil)) + assert.Equal(t, 0, distributionFunction(&cluster1)) + assert.Equal(t, 1, distributionFunction(&cluster2)) + assert.Equal(t, 0, distributionFunction(&cluster3)) + assert.Equal(t, 1, distributionFunction(&cluster4)) } func TestLegacyGetClusterFilterWithFixedShard(t *testing.T) { - shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...) - db := &dbmocks.ArgoDB{} - db.On("GetApplicationControllerReplicas").Return(2) - filter := GetClusterFilter(db, GetDistributionFunction(db, common.DefaultShardingAlgorithm), shardIndex) - assert.False(t, filter(nil)) - assert.False(t, filter(&v1alpha1.Cluster{ID: "1"})) - assert.True(t, filter(&v1alpha1.Cluster{ID: "2"})) - assert.False(t, filter(&v1alpha1.Cluster{ID: "3"})) - assert.True(t, filter(&v1alpha1.Cluster{ID: "4"})) + //shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...) + t.Setenv(common.EnvControllerReplicas, "5") + clusterAccessor, db, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters() + replicasCount := 5 + db.On("GetApplicationControllerReplicas").Return(replicasCount) + filter := GetDistributionFunction(clusterAccessor, common.DefaultShardingAlgorithm, replicasCount) + assert.Equal(t, 0, filter(nil)) + assert.Equal(t, 4, filter(&cluster1)) + assert.Equal(t, 1, filter(&cluster2)) + assert.Equal(t, 2, filter(&cluster3)) + assert.Equal(t, 2, filter(&cluster4)) var fixedShard int64 = 4 - filter = GetClusterFilter(db, GetDistributionFunction(db, common.DefaultShardingAlgorithm), int(fixedShard)) - assert.False(t, filter(&v1alpha1.Cluster{ID: "4", Shard: &fixedShard})) + cluster5 := &v1alpha1.Cluster{ID: "5", Shard: &fixedShard} + clusterAccessor = getClusterAccessor([]v1alpha1.Cluster{cluster1, cluster2, cluster2, cluster4, *cluster5}) + filter = GetDistributionFunction(clusterAccessor, common.DefaultShardingAlgorithm, replicasCount) + assert.Equal(t, int(fixedShard), filter(cluster5)) fixedShard = 1 - filter = GetClusterFilter(db, GetDistributionFunction(db, common.DefaultShardingAlgorithm), int(fixedShard)) - assert.True(t, filter(&v1alpha1.Cluster{Name: "cluster4", ID: "4", Shard: &fixedShard})) + cluster5.Shard = &fixedShard + clusterAccessor = getClusterAccessor([]v1alpha1.Cluster{cluster1, cluster2, cluster2, cluster4, *cluster5}) + filter = GetDistributionFunction(clusterAccessor, common.DefaultShardingAlgorithm, replicasCount) + assert.Equal(t, int(fixedShard), filter(&v1alpha1.Cluster{ID: "4", Shard: &fixedShard})) } func TestRoundRobinGetClusterFilterWithFixedShard(t *testing.T) { - shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...) - db, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters() - db.On("GetApplicationControllerReplicas").Return(2) - filter := GetClusterFilter(db, GetDistributionFunction(db, common.RoundRobinShardingAlgorithm), shardIndex) - assert.False(t, filter(nil)) - assert.False(t, filter(&cluster1)) - assert.True(t, filter(&cluster2)) - assert.False(t, filter(&cluster3)) - assert.True(t, filter(&cluster4)) + //shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...) + t.Setenv(common.EnvControllerReplicas, "4") + clusterAccessor, db, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters() + replicasCount := 4 + db.On("GetApplicationControllerReplicas").Return(replicasCount) + + filter := GetDistributionFunction(clusterAccessor, common.RoundRobinShardingAlgorithm, replicasCount) + assert.Equal(t, filter(nil), 0) + assert.Equal(t, filter(&cluster1), 0) + assert.Equal(t, filter(&cluster2), 1) + assert.Equal(t, filter(&cluster3), 2) + assert.Equal(t, filter(&cluster4), 3) // a cluster with a fixed shard should be processed by the specified exact // same shard unless the specified shard index is greater than the number of replicas. - var fixedShard int64 = 4 - filter = GetClusterFilter(db, GetDistributionFunction(db, common.RoundRobinShardingAlgorithm), int(fixedShard)) - assert.False(t, filter(&v1alpha1.Cluster{Name: "cluster4", ID: "4", Shard: &fixedShard})) + var fixedShard int64 = 1 + cluster5 := v1alpha1.Cluster{Name: "cluster5", ID: "5", Shard: &fixedShard} + clusters := []v1alpha1.Cluster{cluster1, cluster2, cluster3, cluster4, cluster5} + clusterAccessor = getClusterAccessor(clusters) + filter = GetDistributionFunction(clusterAccessor, common.RoundRobinShardingAlgorithm, replicasCount) + assert.Equal(t, int(fixedShard), filter(&cluster5)) fixedShard = 1 - filter = GetClusterFilter(db, GetDistributionFunction(db, common.RoundRobinShardingAlgorithm), int(fixedShard)) - assert.True(t, filter(&v1alpha1.Cluster{Name: "cluster4", ID: "4", Shard: &fixedShard})) -} - -func TestGetClusterFilterLegacyHash(t *testing.T) { - shardIndex := 1 // ensuring that a shard with index 1 will process all the clusters with an "even" id (2,4,6,...) - t.Setenv(common.EnvControllerShardingAlgorithm, "hash") - db, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters() - db.On("GetApplicationControllerReplicas").Return(2) - filter := GetClusterFilter(db, GetDistributionFunction(db, common.LegacyShardingAlgorithm), shardIndex) - assert.False(t, filter(&cluster1)) - assert.True(t, filter(&cluster2)) - assert.False(t, filter(&cluster3)) - assert.True(t, filter(&cluster4)) - - // a cluster with a fixed shard should be processed by the specified exact - // same shard unless the specified shard index is greater than the number of replicas. - var fixedShard int64 = 4 - filter = GetClusterFilter(db, GetDistributionFunction(db, common.LegacyShardingAlgorithm), int(fixedShard)) - assert.False(t, filter(&v1alpha1.Cluster{Name: "cluster4", ID: "4", Shard: &fixedShard})) - - fixedShard = 1 - filter = GetClusterFilter(db, GetDistributionFunction(db, common.LegacyShardingAlgorithm), int(fixedShard)) - assert.True(t, filter(&v1alpha1.Cluster{Name: "cluster4", ID: "4", Shard: &fixedShard})) -} - -func TestGetClusterFilterWithEnvControllerShardingAlgorithms(t *testing.T) { - db, cluster1, cluster2, cluster3, cluster4, _ := createTestClusters() - shardIndex := 1 - db.On("GetApplicationControllerReplicas").Return(2) - - t.Run("legacy", func(t *testing.T) { - t.Setenv(common.EnvControllerShardingAlgorithm, common.LegacyShardingAlgorithm) - shardShouldProcessCluster := GetClusterFilter(db, GetDistributionFunction(db, common.LegacyShardingAlgorithm), shardIndex) - assert.False(t, shardShouldProcessCluster(&cluster1)) - assert.True(t, shardShouldProcessCluster(&cluster2)) - assert.False(t, shardShouldProcessCluster(&cluster3)) - assert.True(t, shardShouldProcessCluster(&cluster4)) - assert.False(t, shardShouldProcessCluster(nil)) - }) - - t.Run("roundrobin", func(t *testing.T) { - t.Setenv(common.EnvControllerShardingAlgorithm, common.RoundRobinShardingAlgorithm) - shardShouldProcessCluster := GetClusterFilter(db, GetDistributionFunction(db, common.LegacyShardingAlgorithm), shardIndex) - assert.False(t, shardShouldProcessCluster(&cluster1)) - assert.True(t, shardShouldProcessCluster(&cluster2)) - assert.False(t, shardShouldProcessCluster(&cluster3)) - assert.True(t, shardShouldProcessCluster(&cluster4)) - assert.False(t, shardShouldProcessCluster(nil)) - }) + cluster5 = v1alpha1.Cluster{Name: "cluster5", ID: "5", Shard: &fixedShard} + clusters = []v1alpha1.Cluster{cluster1, cluster2, cluster3, cluster4, cluster5} + clusterAccessor = getClusterAccessor(clusters) + filter = GetDistributionFunction(clusterAccessor, common.RoundRobinShardingAlgorithm, replicasCount) + assert.Equal(t, int(fixedShard), filter(&v1alpha1.Cluster{Name: "cluster4", ID: "4", Shard: &fixedShard})) } func TestGetShardByIndexModuloReplicasCountDistributionFunction2(t *testing.T) { - db, cluster1, cluster2, cluster3, cluster4, cluster5 := createTestClusters() + clusters, db, cluster1, cluster2, cluster3, cluster4, cluster5 := createTestClusters() t.Run("replicas set to 1", func(t *testing.T) { - db.On("GetApplicationControllerReplicas").Return(1).Once() - distributionFunction := RoundRobinDistributionFunction(db) + replicasCount := 1 + db.On("GetApplicationControllerReplicas").Return(replicasCount).Once() + distributionFunction := RoundRobinDistributionFunction(clusters, replicasCount) assert.Equal(t, 0, distributionFunction(nil)) assert.Equal(t, 0, distributionFunction(&cluster1)) assert.Equal(t, 0, distributionFunction(&cluster2)) @@ -205,8 +183,9 @@ func TestGetShardByIndexModuloReplicasCountDistributionFunction2(t *testing.T) { }) t.Run("replicas set to 2", func(t *testing.T) { - db.On("GetApplicationControllerReplicas").Return(2).Once() - distributionFunction := RoundRobinDistributionFunction(db) + replicasCount := 2 + db.On("GetApplicationControllerReplicas").Return(replicasCount).Once() + distributionFunction := RoundRobinDistributionFunction(clusters, replicasCount) assert.Equal(t, 0, distributionFunction(nil)) assert.Equal(t, 0, distributionFunction(&cluster1)) assert.Equal(t, 1, distributionFunction(&cluster2)) @@ -216,8 +195,9 @@ func TestGetShardByIndexModuloReplicasCountDistributionFunction2(t *testing.T) { }) t.Run("replicas set to 3", func(t *testing.T) { - db.On("GetApplicationControllerReplicas").Return(3).Once() - distributionFunction := RoundRobinDistributionFunction(db) + replicasCount := 3 + db.On("GetApplicationControllerReplicas").Return(replicasCount).Once() + distributionFunction := RoundRobinDistributionFunction(clusters, replicasCount) assert.Equal(t, 0, distributionFunction(nil)) assert.Equal(t, 0, distributionFunction(&cluster1)) assert.Equal(t, 1, distributionFunction(&cluster2)) @@ -233,17 +213,19 @@ func TestGetShardByIndexModuloReplicasCountDistributionFunctionWhenClusterNumber // Initial tests where showing that under 1024 clusters, execution time was around 400ms // and for 4096 clusters, execution time was under 9s // The other implementation was giving almost linear time of 400ms up to 10'000 clusters - db := dbmocks.ArgoDB{} - clusterList := &v1alpha1.ClusterList{Items: []v1alpha1.Cluster{}} + clusterPointers := []*v1alpha1.Cluster{} for i := 0; i < 2048; i++ { cluster := createCluster(fmt.Sprintf("cluster-%d", i), fmt.Sprintf("%d", i)) - clusterList.Items = append(clusterList.Items, cluster) + clusterPointers = append(clusterPointers, &cluster) } - db.On("ListClusters", mock.Anything).Return(clusterList, nil) - db.On("GetApplicationControllerReplicas").Return(2) - distributionFunction := RoundRobinDistributionFunction(&db) - for i, c := range clusterList.Items { - assert.Equal(t, i%2, distributionFunction(&c)) + replicasCount := 2 + t.Setenv(common.EnvControllerReplicas, strconv.Itoa(replicasCount)) + _, db, _, _, _, _, _ := createTestClusters() + clusterAccessor := func() []*v1alpha1.Cluster { return clusterPointers } + db.On("GetApplicationControllerReplicas").Return(replicasCount) + distributionFunction := RoundRobinDistributionFunction(clusterAccessor, replicasCount) + for i, c := range clusterPointers { + assert.Equal(t, i%2, distributionFunction(c)) } } @@ -256,12 +238,15 @@ func TestGetShardByIndexModuloReplicasCountDistributionFunctionWhenClusterIsAdde cluster5 := createCluster("cluster5", "5") cluster6 := createCluster("cluster6", "6") + clusters := []v1alpha1.Cluster{cluster1, cluster2, cluster3, cluster4, cluster5} + clusterAccessor := getClusterAccessor(clusters) + clusterList := &v1alpha1.ClusterList{Items: []v1alpha1.Cluster{cluster1, cluster2, cluster3, cluster4, cluster5}} db.On("ListClusters", mock.Anything).Return(clusterList, nil) - // Test with replicas set to 2 - db.On("GetApplicationControllerReplicas").Return(2) - distributionFunction := RoundRobinDistributionFunction(&db) + replicasCount := 2 + db.On("GetApplicationControllerReplicas").Return(replicasCount) + distributionFunction := RoundRobinDistributionFunction(clusterAccessor, replicasCount) assert.Equal(t, 0, distributionFunction(nil)) assert.Equal(t, 0, distributionFunction(&cluster1)) assert.Equal(t, 1, distributionFunction(&cluster2)) @@ -272,17 +257,20 @@ func TestGetShardByIndexModuloReplicasCountDistributionFunctionWhenClusterIsAdde // Now, the database knows cluster6. Shard should be assigned a proper shard clusterList.Items = append(clusterList.Items, cluster6) + distributionFunction = RoundRobinDistributionFunction(getClusterAccessor(clusterList.Items), replicasCount) assert.Equal(t, 1, distributionFunction(&cluster6)) // Now, we remove the last added cluster, it should be unassigned as well clusterList.Items = clusterList.Items[:len(clusterList.Items)-1] + distributionFunction = RoundRobinDistributionFunction(getClusterAccessor(clusterList.Items), replicasCount) assert.Equal(t, -1, distributionFunction(&cluster6)) } func TestGetShardByIndexModuloReplicasCountDistributionFunction(t *testing.T) { - db, cluster1, cluster2, _, _, _ := createTestClusters() - db.On("GetApplicationControllerReplicas").Return(2) - distributionFunction := RoundRobinDistributionFunction(db) + clusters, db, cluster1, cluster2, _, _, _ := createTestClusters() + replicasCount := 2 + db.On("GetApplicationControllerReplicas").Return(replicasCount) + distributionFunction := RoundRobinDistributionFunction(clusters, replicasCount) // Test that the function returns the correct shard for cluster1 and cluster2 expectedShardForCluster1 := 0 @@ -315,14 +303,14 @@ func TestInferShard(t *testing.T) { osHostnameFunction = func() (string, error) { return "exampleshard", nil } _, err = InferShard() - assert.NotNil(t, err) + assert.Nil(t, err) osHostnameFunction = func() (string, error) { return "example-shard", nil } _, err = InferShard() - assert.NotNil(t, err) + assert.Nil(t, err) } -func createTestClusters() (*dbmocks.ArgoDB, v1alpha1.Cluster, v1alpha1.Cluster, v1alpha1.Cluster, v1alpha1.Cluster, v1alpha1.Cluster) { +func createTestClusters() (clusterAccessor, *dbmocks.ArgoDB, v1alpha1.Cluster, v1alpha1.Cluster, v1alpha1.Cluster, v1alpha1.Cluster, v1alpha1.Cluster) { db := dbmocks.ArgoDB{} cluster1 := createCluster("cluster1", "1") cluster2 := createCluster("cluster2", "2") @@ -330,10 +318,27 @@ func createTestClusters() (*dbmocks.ArgoDB, v1alpha1.Cluster, v1alpha1.Cluster, cluster4 := createCluster("cluster4", "4") cluster5 := createCluster("cluster5", "5") + clusters := []v1alpha1.Cluster{cluster1, cluster2, cluster3, cluster4, cluster5} + db.On("ListClusters", mock.Anything).Return(&v1alpha1.ClusterList{Items: []v1alpha1.Cluster{ cluster1, cluster2, cluster3, cluster4, cluster5, }}, nil) - return &db, cluster1, cluster2, cluster3, cluster4, cluster5 + return getClusterAccessor(clusters), &db, cluster1, cluster2, cluster3, cluster4, cluster5 +} + +func getClusterAccessor(clusters []v1alpha1.Cluster) clusterAccessor { + // Convert the array to a slice of pointers + clusterPointers := getClusterPointers(clusters) + clusterAccessor := func() []*v1alpha1.Cluster { return clusterPointers } + return clusterAccessor +} + +func getClusterPointers(clusters []v1alpha1.Cluster) []*v1alpha1.Cluster { + var clusterPointers []*v1alpha1.Cluster + for i := range clusters { + clusterPointers = append(clusterPointers, &clusters[i]) + } + return clusterPointers } func createCluster(name string, id string) v1alpha1.Cluster { diff --git a/controller/sharding/shuffle_test.go b/controller/sharding/shuffle_test.go index 9e089e31bad0f..1cca783a2afe9 100644 --- a/controller/sharding/shuffle_test.go +++ b/controller/sharding/shuffle_test.go @@ -3,6 +3,7 @@ package sharding import ( "fmt" "math" + "strconv" "testing" "github.com/argoproj/argo-cd/v2/common" @@ -22,9 +23,11 @@ func TestLargeShuffle(t *testing.T) { clusterList.Items = append(clusterList.Items, cluster) } db.On("ListClusters", mock.Anything).Return(clusterList, nil) + clusterAccessor := getClusterAccessor(clusterList.Items) // Test with replicas set to 256 - t.Setenv(common.EnvControllerReplicas, "256") - distributionFunction := RoundRobinDistributionFunction(&db) + replicasCount := 256 + t.Setenv(common.EnvControllerReplicas, strconv.Itoa(replicasCount)) + distributionFunction := RoundRobinDistributionFunction(clusterAccessor, replicasCount) for i, c := range clusterList.Items { assert.Equal(t, i%2567, distributionFunction(&c)) } @@ -44,10 +47,11 @@ func TestShuffle(t *testing.T) { clusterList := &v1alpha1.ClusterList{Items: []v1alpha1.Cluster{cluster1, cluster2, cluster3, cluster4, cluster5, cluster6}} db.On("ListClusters", mock.Anything).Return(clusterList, nil) - + clusterAccessor := getClusterAccessor(clusterList.Items) // Test with replicas set to 3 t.Setenv(common.EnvControllerReplicas, "3") - distributionFunction := RoundRobinDistributionFunction(&db) + replicasCount := 3 + distributionFunction := RoundRobinDistributionFunction(clusterAccessor, replicasCount) assert.Equal(t, 0, distributionFunction(nil)) assert.Equal(t, 0, distributionFunction(&cluster1)) assert.Equal(t, 1, distributionFunction(&cluster2)) diff --git a/docs/assets/api-management.png b/docs/assets/api-management.png deleted file mode 100644 index ae066f0a6a87d..0000000000000 Binary files a/docs/assets/api-management.png and /dev/null differ diff --git a/docs/assets/groups-claim.png b/docs/assets/groups-claim.png deleted file mode 100644 index d27e03b661f82..0000000000000 Binary files a/docs/assets/groups-claim.png and /dev/null differ diff --git a/docs/assets/groups-scope.png b/docs/assets/groups-scope.png deleted file mode 100644 index 45557b51ead7f..0000000000000 Binary files a/docs/assets/groups-scope.png and /dev/null differ diff --git a/docs/assets/okta-app.png b/docs/assets/okta-app.png new file mode 100644 index 0000000000000..bfc4570826b0a Binary files /dev/null and b/docs/assets/okta-app.png differ diff --git a/docs/assets/okta-auth-policy.png b/docs/assets/okta-auth-policy.png new file mode 100644 index 0000000000000..dbf99a88ed6e3 Binary files /dev/null and b/docs/assets/okta-auth-policy.png differ diff --git a/docs/assets/okta-auth-rule.png b/docs/assets/okta-auth-rule.png new file mode 100644 index 0000000000000..4e85b062f357b Binary files /dev/null and b/docs/assets/okta-auth-rule.png differ diff --git a/docs/assets/okta-create-oidc-app.png b/docs/assets/okta-create-oidc-app.png new file mode 100644 index 0000000000000..cf0b75b0e4a21 Binary files /dev/null and b/docs/assets/okta-create-oidc-app.png differ diff --git a/docs/assets/okta-groups-claim.png b/docs/assets/okta-groups-claim.png new file mode 100644 index 0000000000000..4edb93d42ea91 Binary files /dev/null and b/docs/assets/okta-groups-claim.png differ diff --git a/docs/assets/okta-groups-scope.png b/docs/assets/okta-groups-scope.png new file mode 100644 index 0000000000000..6cd1783c72653 Binary files /dev/null and b/docs/assets/okta-groups-scope.png differ diff --git a/docs/cli_installation.md b/docs/cli_installation.md index 42938bcd751ba..5a314d4ce6be2 100644 --- a/docs/cli_installation.md +++ b/docs/cli_installation.md @@ -37,6 +37,17 @@ sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd rm argocd-linux-amd64 ``` +#### Download latest stable version + +You can download the latest stable release by executing below steps: + +```bash +VERSION=$(curl -L -s https://raw.githubusercontent.com/argoproj/argo-cd/stable/VERSION) +curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/download/v$VERSION/argocd-linux-amd64 +sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd +rm argocd-linux-amd64 +``` + You should now be able to run `argocd` commands. diff --git a/docs/developer-guide/site.md b/docs/developer-guide/site.md index af32753a323e2..efd6aece9aedb 100644 --- a/docs/developer-guide/site.md +++ b/docs/developer-guide/site.md @@ -7,20 +7,14 @@ The website is built using `mkdocs` and `mkdocs-material`. To test: ```bash +make build-docs make serve-docs ``` - Once running, you can view your locally built documentation at [http://0.0.0.0:8000/](http://0.0.0.0:8000/). -## Deploying - -```bash -make publish-docs -``` - ## Analytics !!! tip Don't forget to disable your ad-blocker when testing. -We collect [Google Analytics](https://analytics.google.com/analytics/web/#/report-home/a105170809w198079555p192782995). \ No newline at end of file +We collect [Google Analytics](https://analytics.google.com/analytics/web/#/report-home/a105170809w198079555p192782995). diff --git a/docs/faq.md b/docs/faq.md index 95205ae6bbb4e..83bdf8d7d38b5 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -36,7 +36,7 @@ which might cause health check to return `Progressing` state instead of `Healthy As workaround Argo CD allows providing [health check](operator-manual/health.md) customization which overrides default behavior. -If you are using Traefik for your Ingress, you can update the Traefik config to publish the loadBalancer IP using [publishedservice](https://doc.traefik.io/traefik/providers/kubernetes-ingress/#publishedservice), which will resolve this issue. +If you are using Traefik for your Ingress, you can update the Traefik config to publish the loadBalancer IP using [publishedservice](https://doc.traefik.io/traefik/providers/kubernetes-ingress/#publishedservice), which will resolve this issue. ```yaml providers: @@ -97,7 +97,7 @@ data: ## After deploying my Helm application with Argo CD I cannot see it with `helm ls` and other Helm commands -When deploying a Helm application Argo CD is using Helm +When deploying a Helm application Argo CD is using Helm only as a template mechanism. It runs `helm template` and then deploys the resulting manifests on the cluster instead of doing `helm install`. This means that you cannot use any Helm command to view/verify the application. It is fully managed by Argo CD. @@ -140,15 +140,15 @@ Argo CD automatically sets the `app.kubernetes.io/instance` label and uses it to If the tool does this too, this causes confusion. You can change this label by setting the `application.instanceLabelKey` value in the `argocd-cm`. We recommend that you use `argocd.argoproj.io/instance`. -!!! note +!!! note When you make this change your applications will become out of sync and will need re-syncing. See [#1482](https://github.com/argoproj/argo-cd/issues/1482). ## How often does Argo CD check for changes to my Git or Helm repository ? -The default polling interval is 3 minutes (180 seconds). -You can change the setting by updating the `timeout.reconciliation` value in the [argocd-cm](https://github.com/argoproj/argo-cd/blob/2d6ce088acd4fb29271ffb6f6023dbb27594d59b/docs/operator-manual/argocd-cm.yaml#L279-L282) config map. If there are any Git changes, Argo CD will only update applications with the [auto-sync setting](user-guide/auto_sync.md) enabled. If you set it to `0` then Argo CD will stop polling Git repositories automatically and you can only use alternative methods such as [webhooks](operator-manual/webhook.md) and/or manual syncs for deploying applications. +The default polling interval is 3 minutes (180 seconds) with a configurable jitter. +You can change the setting by updating the `timeout.reconciliation` value and the `timeout.reconciliation.jitter` in the [argocd-cm](https://github.com/argoproj/argo-cd/blob/2d6ce088acd4fb29271ffb6f6023dbb27594d59b/docs/operator-manual/argocd-cm.yaml#L279-L282) config map. If there are any Git changes, Argo CD will only update applications with the [auto-sync setting](user-guide/auto_sync.md) enabled. If you set it to `0` then Argo CD will stop polling Git repositories automatically and you can only use alternative methods such as [webhooks](operator-manual/webhook.md) and/or manual syncs for deploying applications. ## Why Are My Resource Limits `Out Of Sync`? @@ -250,7 +250,7 @@ There are two parts to the message: > map[name:**KEY_BC** value:150] map[name:**KEY_BC** value:500] map[name:**KEY_BD** value:250] map[name:**KEY_BD** value:500] map[name:KEY_BI value:something] - You'll want to identify the keys that are duplicated -- you can focus on the first part, as each duplicated key will appear, once for each of its value with its value in the first list. The second list is really just + You'll want to identify the keys that are duplicated -- you can focus on the first part, as each duplicated key will appear, once for each of its value with its value in the first list. The second list is really just `]` @@ -259,7 +259,7 @@ There are two parts to the message: This includes all of the keys. It's included for debugging purposes -- you don't need to pay much attention to it. It will give you a hint about the precise location in the list for the duplicated keys: > map[name:KEY_AA] map[name:KEY_AB] map[name:KEY_AC] map[name:KEY_AD] map[name:KEY_AE] map[name:KEY_AF] map[name:KEY_AG] map[name:KEY_AH] map[name:KEY_AI] map[name:KEY_AJ] map[name:KEY_AK] map[name:KEY_AL] map[name:KEY_AM] map[name:KEY_AN] map[name:KEY_AO] map[name:KEY_AP] map[name:KEY_AQ] map[name:KEY_AR] map[name:KEY_AS] map[name:KEY_AT] map[name:KEY_AU] map[name:KEY_AV] map[name:KEY_AW] map[name:KEY_AX] map[name:KEY_AY] map[name:KEY_AZ] map[name:KEY_BA] map[name:KEY_BB] map[name:**KEY_BC**] map[name:**KEY_BD**] map[name:KEY_BE] map[name:KEY_BF] map[name:KEY_BG] map[name:KEY_BH] map[name:KEY_BI] map[name:**KEY_BC**] map[name:**KEY_BD**] - + `]` In this case, the duplicated keys have been **emphasized** to help you identify the problematic keys. Many editors have the ability to highlight all instances of a string, using such an editor can help with such problems. diff --git a/docs/operator-manual/application.yaml b/docs/operator-manual/application.yaml index aa2dea5c65b7c..864a293ce6890 100644 --- a/docs/operator-manual/application.yaml +++ b/docs/operator-manual/application.yaml @@ -119,7 +119,7 @@ spec: extVars: - name: foo value: bar - # You can use "code to determine if the value is either string (false, the default) or Jsonnet code (if code is true). + # You can use "code" to determine if the value is either string (false, the default) or Jsonnet code (if code is true). - code: true name: baz value: "true" diff --git a/docs/operator-manual/applicationset/GoTemplate.md b/docs/operator-manual/applicationset/GoTemplate.md index 4a2b6cf55140b..1d62eeea9f93a 100644 --- a/docs/operator-manual/applicationset/GoTemplate.md +++ b/docs/operator-manual/applicationset/GoTemplate.md @@ -12,7 +12,7 @@ An additional `normalize` function makes any string parameter usable as a valid with hyphens and truncating at 253 characters. This is useful when making parameters safe for things like Application names. -Another function has `slugify` function has been added which, by default, sanitizes and smart truncate (means doesn't cut a word into 2). This function accepts a couple of arguments: +Another `slugify` function has been added which, by default, sanitizes and smart truncates (it doesn't cut a word into 2). This function accepts a couple of arguments: - The first argument (if provided) is an integer specifying the maximum length of the slug. - The second argument (if provided) is a boolean indicating whether smart truncation is enabled. - The last argument (if provided) is the input name that needs to be slugified. @@ -206,6 +206,8 @@ ApplicationSet controller provides: 1. contains no more than 253 characters 2. contains only lowercase alphanumeric characters, '-' or '.' 3. starts and ends with an alphanumeric character + +- `slugify`: sanitizes like `normalize` and smart truncates (it doesn't cut a word into 2) like described in the [introduction](#introduction) section. - `toYaml` / `fromYaml` / `fromYamlArray` helm like functions diff --git a/docs/operator-manual/applicationset/Template.md b/docs/operator-manual/applicationset/Template.md index 573e297bff2e2..9a7cd574453b4 100644 --- a/docs/operator-manual/applicationset/Template.md +++ b/docs/operator-manual/applicationset/Template.md @@ -111,16 +111,15 @@ In this example, the ApplicationSet controller will generate an `Application` re ## Template Patch -Templating is only available on string type. However, some uses cases may require to apply templating on other types. +Templating is only available on string type. However, some use cases may require applying templating on other types. Example: -- Set the automated sync policy -- Switch prune boolean to true -- Add multiple helm value files - -Argo CD has a `templatePatch` feature to allow advanced templating. It supports both json and yaml. +- Conditionally set the automated sync policy. +- Conditionally switch prune boolean to `true`. +- Add multiple helm value files from a list. +The `templatePatch` feature enables advanced templating, with support for `json` and `yaml`. ```yaml apiVersion: argoproj.io/v1alpha1 @@ -174,3 +173,6 @@ spec: The `spec.project` field is not supported in `templatePatch`. If you need to change the project, you can use the `spec.project` field in the `template` field. + +!!! important + When writing a `templatePatch`, you're crafting a patch. So, if the patch includes an empty `spec: # nothing in here`, it will effectively clear out existing fields. See [#17040](https://github.com/argoproj/argo-cd/issues/17040) for an example of this behavior. diff --git a/docs/operator-manual/argocd-cm.yaml b/docs/operator-manual/argocd-cm.yaml index 5e4ed095be56d..4355354d2faef 100644 --- a/docs/operator-manual/argocd-cm.yaml +++ b/docs/operator-manual/argocd-cm.yaml @@ -308,14 +308,22 @@ data: # have either a permanent banner or a regular closeable banner, and NOT both. eg. A user can't dismiss a # notification message (closeable) banner, to then immediately see a permanent banner. # ui.bannerpermanent: "true" - # An option to specify the position of the banner, either the top or bottom of the page. The default is at the top. - # Uncomment to make the banner appear at the bottom of the page. Any value other than "bottom" will make the banner appear at the top. + # An option to specify the position of the banner, either the top or bottom of the page, or both. The valid values + # are: "top", "bottom" and "both". The default (if the option is not provided), is "top". If "both" is specified, then + # the content appears both at the top and the bottom of the page. Uncomment the following line to make the banner appear + # at the bottom of the page. Change the value as needed. # ui.bannerposition: "bottom" # Application reconciliation timeout is the max amount of time required to discover if a new manifests version got # published to the repository. Reconciliation by timeout is disabled if timeout is set to 0. Three minutes by default. # > Note: argocd-repo-server deployment must be manually restarted after changing the setting. timeout.reconciliation: 180s + # With a large number of applications, the periodic refresh for each application can cause a spike in the refresh queue + # and can cause a spike in the repo-server component. To avoid this, you can set a jitter to the sync timeout, which will + # spread out the refreshes and give time to the repo-server to catch up. The jitter is the maximum duration that can be + # added to the sync timeout. So, if the sync timeout is 3 minutes and the jitter is 1 minute, then the actual timeout will + # be between 3 and 4 minutes. Disabled when the value is 0, defaults to 0. + timeout.reconciliation.jitter: 0 # cluster.inClusterEnabled indicates whether to allow in-cluster server address. This is enabled by default. cluster.inClusterEnabled: "true" diff --git a/docs/operator-manual/argocd-cmd-params-cm.yaml b/docs/operator-manual/argocd-cmd-params-cm.yaml index dac955a9662de..3cb79d85f3150 100644 --- a/docs/operator-manual/argocd-cmd-params-cm.yaml +++ b/docs/operator-manual/argocd-cmd-params-cm.yaml @@ -90,6 +90,9 @@ data: server.k8sclient.retry.max: "0" # The initial backoff delay on the first retry attempt in ms. Subsequent retries will double this backoff time up to a maximum threshold server.k8sclient.retry.base.backoff: "100" + # Semicolon-separated list of content types allowed on non-GET requests. Set an empty string to allow all. Be aware + # that allowing content types besides application/json may make your API more vulnerable to CSRF attacks. + server.api.content.types: "application/json" # Set the logging format. One of: text|json (default "text") server.log.format: "text" diff --git a/docs/operator-manual/declarative-setup.md b/docs/operator-manual/declarative-setup.md index c1f5ba2b2d3bd..4d87ae9f80286 100644 --- a/docs/operator-manual/declarative-setup.md +++ b/docs/operator-manual/declarative-setup.md @@ -549,6 +549,7 @@ bearerToken: string awsAuthConfig: clusterName: string roleARN: string + profile: string # Configure external command to supply client credentials # See https://godoc.org/k8s.io/client-go/tools/clientcmd/api#ExecConfig execProviderConfig: diff --git a/docs/operator-manual/dynamic-cluster-distribution.md b/docs/operator-manual/dynamic-cluster-distribution.md index a32258c3f2f0a..9d5d2104a1795 100644 --- a/docs/operator-manual/dynamic-cluster-distribution.md +++ b/docs/operator-manual/dynamic-cluster-distribution.md @@ -17,16 +17,10 @@ which does not require a restart of the application controller pods. ## Enabling Dynamic Distribution of Clusters -This feature is disabled by default while it is in alpha. To enable it, you must set the environment `ARGOCD_ENABLE_DYNAMIC_CLUSTER_DISTRIBUTION` to true when running the Application Controller. - -In order to utilize the feature, the manifests `manifests/ha/base/controller-deployment/` can be applied as a Kustomize -overlay. This overlay sets the StatefulSet replicas to `0` and deploys the application controller as a Deployment. The -dynamic distribution code automatically kicks in when the controller is deployed as a Deployment. +This feature is disabled by default while it is in alpha. In order to utilize the feature, the manifests `manifests/ha/base/controller-deployment/` can be applied as a Kustomize overlay. This overlay sets the StatefulSet replicas to `0` and deploys the application controller as a Deployment. Also, you must set the environment `ARGOCD_ENABLE_DYNAMIC_CLUSTER_DISTRIBUTION` to true when running the Application Controller as a deployment. !!! important - The use of a Deployment instead of a StatefulSet is an implementation detail which may change in future versions of - this feature. Therefore, the directory name of the Kustomize overlay may change as well. Monitor the release notes - to avoid issues. + The use of a Deployment instead of a StatefulSet is an implementation detail which may change in future versions of this feature. Therefore, the directory name of the Kustomize overlay may change as well. Monitor the release notes to avoid issues. Note the introduction of new environment variable `ARGOCD_CONTROLLER_HEARTBEAT_TIME`. The environment variable is explained in [working of Dynamic Distribution Heartbeat Process](#working-of-dynamic-distribution) diff --git a/docs/operator-manual/high_availability.md b/docs/operator-manual/high_availability.md index b2051ad1a152c..0a011104967f1 100644 --- a/docs/operator-manual/high_availability.md +++ b/docs/operator-manual/high_availability.md @@ -57,7 +57,7 @@ performance. For performance reasons the controller monitors and caches only the preferred version into a version of the resource stored in Git. If `kubectl convert` fails because the conversion is not supported then the controller falls back to Kubernetes API query which slows down reconciliation. In this case, we advise to use the preferred resource version in Git. -* The controller polls Git every 3m by default. You can change this duration using the `timeout.reconciliation` setting in the `argocd-cm` ConfigMap. The value of `timeout.reconciliation` is a duration string e.g `60s`, `1m`, `1h` or `1d`. +* The controller polls Git every 3m by default. You can change this duration using the `timeout.reconciliation` and `timeout.reconciliation.jitter` setting in the `argocd-cm` ConfigMap. The value of the fields is a duration string e.g `60s`, `1m`, `1h` or `1d`. * If the controller is managing too many clusters and uses too much memory then you can shard clusters across multiple controller replicas. To enable sharding, increase the number of replicas in `argocd-application-controller` `StatefulSet` @@ -244,30 +244,41 @@ spec: # ... ``` +### Application Sync Timeout & Jitter + +Argo CD has a timeout for application syncs. It will trigger a refresh for each application periodically when the timeout expires. +With a large number of applications, this will cause a spike in the refresh queue and can cause a spike to the repo-server component. To avoid this, you can set a jitter to the sync timeout which will spread out the refreshes and give time to the repo-server to catch up. + +The jitter is the maximum duration that can be added to the sync timeout, so if the sync timeout is 5 minutes and the jitter is 1 minute, then the actual timeout will be between 5 and 6 minutes. + +To configure the jitter you can set the following environment variables: + +* `ARGOCD_RECONCILIATION_JITTER` - The jitter to apply to the sync timeout. Disabled when value is 0. Defaults to 0. + ## Rate Limiting Application Reconciliations -To prevent high controller resource usage or sync loops caused either due to misbehaving apps or other environment specific factors, +To prevent high controller resource usage or sync loops caused either due to misbehaving apps or other environment specific factors, we can configure rate limits on the workqueues used by the application controller. There are two types of rate limits that can be configured: * Global rate limits * Per item rate limits -The final rate limiter uses a combination of both and calculates the final backoff as `max(globalBackoff, perItemBackoff)`. +The final rate limiter uses a combination of both and calculates the final backoff as `max(globalBackoff, perItemBackoff)`. ### Global rate limits This is enabled by default, it is a simple bucket based rate limiter that limits the number of items that can be queued per second. -This is useful to prevent a large number of apps from being queued at the same time. - +This is useful to prevent a large number of apps from being queued at the same time. + To configure the bucket limiter you can set the following environment variables: * `WORKQUEUE_BUCKET_SIZE` - The number of items that can be queued in a single burst. Defaults to 500. * `WORKQUEUE_BUCKET_QPS` - The number of items that can be queued per second. Defaults to 50. -### Per item rate limits +### Per item rate limits - This by default returns a fixed base delay/backoff value but can be configured to return exponential values, read further to understand it's working. -Per item rate limiter limits the number of times a particular item can be queued. This is based on exponential backoff where the backoff time for an item keeps increasing exponentially + This by default returns a fixed base delay/backoff value but can be configured to return exponential values. +Per item rate limiter limits the number of times a particular item can be queued. This is based on exponential backoff where the backoff time for an item keeps increasing exponentially if it is queued multiple times in a short period, but the backoff is reset automatically if a configured `cool down` period has elapsed since the last time the item was queued. To configure the per item limiter you can set the following environment variables: @@ -277,16 +288,16 @@ To configure the per item limiter you can set the following environment variable * `WORKQUEUE_MAX_DELAY_NS` : The max delay in nanoseconds, this is the max backoff limit. Defaults to 3 * 10^9 (=3s) * `WORKQUEUE_BACKOFF_FACTOR` : The backoff factor, this is the factor by which the backoff is increased for each retry. Defaults to 1.5 -The formula used to calculate the backoff time for an item, where `numRequeue` is the number of times the item has been queued +The formula used to calculate the backoff time for an item, where `numRequeue` is the number of times the item has been queued and `lastRequeueTime` is the time at which the item was last queued: - When `WORKQUEUE_FAILURE_COOLDOWN_NS` != 0 : ``` -backoff = time.Since(lastRequeueTime) >= WORKQUEUE_FAILURE_COOLDOWN_NS ? - WORKQUEUE_BASE_DELAY_NS : +backoff = time.Since(lastRequeueTime) >= WORKQUEUE_FAILURE_COOLDOWN_NS ? + WORKQUEUE_BASE_DELAY_NS : min( - WORKQUEUE_MAX_DELAY_NS, + WORKQUEUE_MAX_DELAY_NS, WORKQUEUE_BASE_DELAY_NS * WORKQUEUE_BACKOFF_FACTOR ^ (numRequeue) ) ``` diff --git a/docs/operator-manual/ingress.md b/docs/operator-manual/ingress.md index 5ea947345d507..aad2208c21873 100644 --- a/docs/operator-manual/ingress.md +++ b/docs/operator-manual/ingress.md @@ -166,6 +166,43 @@ The argocd-server Service needs to be annotated with `projectcontour.io/upstream The API server should then be run with TLS disabled. Edit the `argocd-server` deployment to add the `--insecure` flag to the argocd-server command, or simply set `server.insecure: "true"` in the `argocd-cmd-params-cm` ConfigMap [as described here](server-commands/additional-configuration-method.md). +Contour httpproxy CRD: + +Using a contour httpproxy CRD allows you to use the same hostname for the GRPC and REST api. + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: argocd-server + namespace: argocd +spec: + ingressClassName: contour + virtualhost: + fqdn: path.to.argocd.io + tls: + secretName: wildcard-tls + routes: + - conditions: + - prefix: / + - header: + name: Content-Type + contains: application/grpc + services: + - name: argocd-server + port: 80 + protocol: h2c # allows for unencrypted http2 connections + timeoutPolicy: + response: 1h + idle: 600s + idleConnection: 600s + - conditions: + - prefix: / + services: + - name: argocd-server + port: 80 +``` + ## [kubernetes/ingress-nginx](https://github.com/kubernetes/ingress-nginx) ### Option 1: SSL-Passthrough diff --git a/docs/operator-manual/metrics.md b/docs/operator-manual/metrics.md index cfd2a8a8093ac..634684a430045 100644 --- a/docs/operator-manual/metrics.md +++ b/docs/operator-manual/metrics.md @@ -70,6 +70,8 @@ Scraped at the `argocd-server-metrics:8083/metrics` endpoint. | `argocd_redis_request_total` | counter | Number of Kubernetes requests executed during application reconciliation. | | `grpc_server_handled_total` | counter | Total number of RPCs completed on the server, regardless of success or failure. | | `grpc_server_msg_sent_total` | counter | Total number of gRPC stream messages sent by the server. | +| `argocd_proxy_extension_request_total` | counter | Number of requests sent to the configured proxy extensions. | +| `argocd_proxy_extension_request_duration_seconds` | histogram | Request duration in seconds between the Argo CD API server and the proxy extension backend. | ## Repo Server Metrics Metrics about the Repo Server. diff --git a/docs/operator-manual/notifications/services/alertmanager.md b/docs/operator-manual/notifications/services/alertmanager.md index e0f9d7e4e7889..033a76a29ea65 100755 --- a/docs/operator-manual/notifications/services/alertmanager.md +++ b/docs/operator-manual/notifications/services/alertmanager.md @@ -43,7 +43,7 @@ You should turn off "send_resolved" or you will receive unnecessary recovery not apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.alertmanager: | targets: @@ -58,7 +58,7 @@ If your alertmanager has changed the default api, you can customize "apiPath". apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.alertmanager: | targets: @@ -89,7 +89,7 @@ stringData: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.alertmanager: | targets: @@ -110,7 +110,7 @@ data: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.alertmanager: | targets: diff --git a/docs/operator-manual/notifications/services/awssqs.md b/docs/operator-manual/notifications/services/awssqs.md index 6b744f4744b93..5331533826348 100755 --- a/docs/operator-manual/notifications/services/awssqs.md +++ b/docs/operator-manual/notifications/services/awssqs.md @@ -1,8 +1,8 @@ -# AWS SQS +# AWS SQS ## Parameters -This notification service is capable of sending simple messages to AWS SQS queue. +This notification service is capable of sending simple messages to AWS SQS queue. * `queue` - name of the queue you are intending to send messages to. Can be overridden with target destination annotation. * `region` - region of the sqs queue can be provided via env variable AWS_DEFAULT_REGION @@ -30,7 +30,7 @@ metadata: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.awssqs: | region: "us-east-2" @@ -63,7 +63,7 @@ stringData: ### Minimal configuration using AWS Env variables -Ensure following list of environment variables are injected via OIDC, or other method. And assuming SQS is local to the account. +Ensure the following list of environment variables are injected via OIDC, or another method. And assuming SQS is local to the account. You may skip usage of secret for sensitive data and omit other parameters. (Setting parameters via ConfigMap takes precedent.) Variables: @@ -89,7 +89,7 @@ metadata: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.awssqs: | queue: "myqueue" @@ -104,3 +104,16 @@ data: - oncePer: obj.metadata.annotations["generation"] ``` + +## FIFO SQS Queues + +FIFO queues require a [MessageGroupId](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessage.html#SQS-SendMessage-request-MessageGroupId) to be sent along with every message, every message with a matching MessageGroupId will be processed one by one in order. + +To send to a FIFO SQS Queue you must include a `messageGroupId` in the template such as in the example below: + +```yaml +template.deployment-ready: | + message: | + Deployment {{.obj.metadata.name}} is ready! + messageGroupId: {{.obj.metadata.name}}-deployment +``` diff --git a/docs/operator-manual/notifications/services/email.md b/docs/operator-manual/notifications/services/email.md index b81ab6cde8b4c..7fd3f0e22379c 100755 --- a/docs/operator-manual/notifications/services/email.md +++ b/docs/operator-manual/notifications/services/email.md @@ -20,7 +20,7 @@ The following snippet contains sample Gmail service configuration: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.email.gmail: | username: $email-username @@ -36,7 +36,7 @@ Without authentication: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.email.example: | host: smtp.example.com @@ -52,7 +52,7 @@ data: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: template.app-sync-succeeded: | email: diff --git a/docs/operator-manual/notifications/services/github.md b/docs/operator-manual/notifications/services/github.md index be76ab150d1a1..1fa1a985d2682 100755 --- a/docs/operator-manual/notifications/services/github.md +++ b/docs/operator-manual/notifications/services/github.md @@ -24,7 +24,7 @@ in `argocd-notifications-cm` ConfigMap apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.github: | appID: @@ -76,6 +76,7 @@ template.app-deployed: | logURL: "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true" requiredContexts: [] autoMerge: true + transientEnvironment: false pullRequestComment: content: | Application {{.app.metadata.name}} is now running new version of deployments manifests. diff --git a/docs/operator-manual/notifications/services/googlechat.md b/docs/operator-manual/notifications/services/googlechat.md index 885ce685a4511..821c23023e863 100755 --- a/docs/operator-manual/notifications/services/googlechat.md +++ b/docs/operator-manual/notifications/services/googlechat.md @@ -19,7 +19,7 @@ The Google Chat notification service send message notifications to a google chat apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.googlechat: | webhooks: diff --git a/docs/operator-manual/notifications/services/grafana.md b/docs/operator-manual/notifications/services/grafana.md index a36672d0fa423..1f3e77701f044 100755 --- a/docs/operator-manual/notifications/services/grafana.md +++ b/docs/operator-manual/notifications/services/grafana.md @@ -21,7 +21,7 @@ Available parameters : apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.grafana: | apiUrl: https://grafana.example.com/api diff --git a/docs/operator-manual/notifications/services/mattermost.md b/docs/operator-manual/notifications/services/mattermost.md index 98e0d0fd7b82f..d1f187e955b9c 100755 --- a/docs/operator-manual/notifications/services/mattermost.md +++ b/docs/operator-manual/notifications/services/mattermost.md @@ -19,7 +19,7 @@ in `argocd-notifications-cm` ConfigMap apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.mattermost: | apiURL: diff --git a/docs/operator-manual/notifications/services/newrelic.md b/docs/operator-manual/notifications/services/newrelic.md index d98288a846422..b0c7e340c9b28 100755 --- a/docs/operator-manual/notifications/services/newrelic.md +++ b/docs/operator-manual/notifications/services/newrelic.md @@ -14,7 +14,7 @@ apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.newrelic: | apiURL: diff --git a/docs/operator-manual/notifications/services/opsgenie.md b/docs/operator-manual/notifications/services/opsgenie.md index 665d0081e7c73..e92ee99756ab8 100755 --- a/docs/operator-manual/notifications/services/opsgenie.md +++ b/docs/operator-manual/notifications/services/opsgenie.md @@ -12,14 +12,15 @@ To be able to send notifications with argocd-notifications you have to create an 8. Give your integration a name, copy the "API key" and safe it somewhere for later 9. Make sure the checkboxes for "Create and Update Access" and "enable" are selected, disable the other checkboxes to remove unnecessary permissions 10. Click "Safe Integration" at the bottom -11. Check your browser for the correct server apiURL. If it is "app.opsgenie.com" then use the us/international api url `api.opsgenie.com` in the next step, otherwise use `api.eu.opsgenie.com` (european api). -12. You are finished with configuring opsgenie. Now you need to configure argocd-notifications. Use the apiUrl, the team name and the apiKey to configure the opsgenie integration in the `argocd-notifications-secret` secret. +11. Check your browser for the correct server apiURL. If it is "app.opsgenie.com" then use the US/international api url `api.opsgenie.com` in the next step, otherwise use `api.eu.opsgenie.com` (European API). +12. You are finished with configuring Opsgenie. Now you need to configure argocd-notifications. Use the apiUrl, the team name and the apiKey to configure the Opsgenie integration in the `argocd-notifications-secret` secret. + ```yaml apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.opsgenie: | apiUrl: diff --git a/docs/operator-manual/notifications/services/pagerduty.md b/docs/operator-manual/notifications/services/pagerduty.md index 0e1ab965332e1..c6e1e41dac81d 100755 --- a/docs/operator-manual/notifications/services/pagerduty.md +++ b/docs/operator-manual/notifications/services/pagerduty.md @@ -1,17 +1,17 @@ -# Pagerduty +# PagerDuty ## Parameters -The Pagerduty notification service is used to create pagerduty incidents and requires specifying the following settings: +The PagerDuty notification service is used to create PagerDuty incidents and requires specifying the following settings: -* `pagerdutyToken` - the pagerduty auth token +* `pagerdutyToken` - the PagerDuty auth token * `from` - email address of a valid user associated with the account making the request. * `serviceID` - The ID of the resource. ## Example -The following snippet contains sample Pagerduty service configuration: +The following snippet contains sample PagerDuty service configuration: ```yaml apiVersion: v1 @@ -26,7 +26,7 @@ stringData: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.pagerduty: | token: $pagerdutyToken @@ -35,13 +35,13 @@ data: ## Template -[Notification templates](../templates.md) support specifying subject for pagerduty notifications: +[Notification templates](../templates.md) support specifying subject for PagerDuty notifications: ```yaml apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: template.rollout-aborted: | message: Rollout {{.rollout.metadata.name}} is aborted. @@ -62,5 +62,5 @@ apiVersion: argoproj.io/v1alpha1 kind: Rollout metadata: annotations: - notifications.argoproj.io/subscribe.on-rollout-aborted.pagerduty: "" + notifications.argoproj.io/subscribe.on-rollout-aborted.pagerduty: "" ``` diff --git a/docs/operator-manual/notifications/services/pagerduty_v2.md b/docs/operator-manual/notifications/services/pagerduty_v2.md index 21e8d942e4e93..549cdc937b150 100755 --- a/docs/operator-manual/notifications/services/pagerduty_v2.md +++ b/docs/operator-manual/notifications/services/pagerduty_v2.md @@ -28,7 +28,7 @@ stringData: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.pagerdutyv2: | serviceKeys: @@ -43,7 +43,7 @@ data: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: template.rollout-aborted: | message: Rollout {{.rollout.metadata.name}} is aborted. @@ -74,5 +74,5 @@ apiVersion: argoproj.io/v1alpha1 kind: Rollout metadata: annotations: - notifications.argoproj.io/subscribe.on-rollout-aborted.pagerdutyv2: "" + notifications.argoproj.io/subscribe.on-rollout-aborted.pagerdutyv2: "" ``` diff --git a/docs/operator-manual/notifications/services/pushover.md b/docs/operator-manual/notifications/services/pushover.md index 37cb20b277dcc..a09b3660f9233 100755 --- a/docs/operator-manual/notifications/services/pushover.md +++ b/docs/operator-manual/notifications/services/pushover.md @@ -1,13 +1,13 @@ # Pushover 1. Create an app at [pushover.net](https://pushover.net/apps/build). -2. Store the API key in `` Secret and define the secret name in `` ConfigMap: +2. Store the API key in `` Secret and define the secret name in `argocd-notifications-cm` ConfigMap: ```yaml apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.pushover: | token: $pushover-token diff --git a/docs/operator-manual/notifications/services/rocketchat.md b/docs/operator-manual/notifications/services/rocketchat.md index f1157050139d0..20aaa405c80d0 100755 --- a/docs/operator-manual/notifications/services/rocketchat.md +++ b/docs/operator-manual/notifications/services/rocketchat.md @@ -43,7 +43,7 @@ stringData: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.rocketchat: | email: $rocketchat-email diff --git a/docs/operator-manual/notifications/services/slack.md b/docs/operator-manual/notifications/services/slack.md index 0f3fdf1739210..41bdddd7617c4 100755 --- a/docs/operator-manual/notifications/services/slack.md +++ b/docs/operator-manual/notifications/services/slack.md @@ -15,6 +15,7 @@ The Slack notification service configuration includes following settings: | `signingSecret` | False | `string` | | `8f742231b10e8888abcd99yyyzzz85a5` | | `token` | **True** | `string` | The app's OAuth access token. | `xoxb-1234567890-1234567890123-5n38u5ed63fgzqlvuyxvxcx6` | | `username` | False | `string` | The app username. | `argocd` | +| `disableUnfurl` | False | `bool` | Disable slack unfurling links in messages | `true` | ## Configuration @@ -48,7 +49,7 @@ The Slack notification service configuration includes following settings: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.slack: | token: $slack-token diff --git a/docs/operator-manual/notifications/services/teams.md b/docs/operator-manual/notifications/services/teams.md index 8b8c6b819c795..0e44456d4de19 100755 --- a/docs/operator-manual/notifications/services/teams.md +++ b/docs/operator-manual/notifications/services/teams.md @@ -18,7 +18,7 @@ The Teams notification service send message notifications using Teams bot and re apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.teams: | recipientUrls: diff --git a/docs/operator-manual/notifications/services/telegram.md b/docs/operator-manual/notifications/services/telegram.md index 953c2a9fca0bf..8612a09d1ca84 100755 --- a/docs/operator-manual/notifications/services/telegram.md +++ b/docs/operator-manual/notifications/services/telegram.md @@ -2,13 +2,13 @@ 1. Get an API token using [@Botfather](https://t.me/Botfather). 2. Store token in `` Secret and configure telegram integration -in `` ConfigMap: +in `argocd-notifications-cm` ConfigMap: ```yaml apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.telegram: | token: $telegram-token diff --git a/docs/operator-manual/notifications/services/webex.md b/docs/operator-manual/notifications/services/webex.md index 440ed1ddc738f..eba4c5e11b8dc 100755 --- a/docs/operator-manual/notifications/services/webex.md +++ b/docs/operator-manual/notifications/services/webex.md @@ -24,7 +24,7 @@ The Webex Teams notification service configuration includes following settings: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.webex: | token: $webex-token diff --git a/docs/operator-manual/notifications/services/webhook.md b/docs/operator-manual/notifications/services/webhook.md index 965098402236f..4b8ca38a685ad 100755 --- a/docs/operator-manual/notifications/services/webhook.md +++ b/docs/operator-manual/notifications/services/webhook.md @@ -31,7 +31,7 @@ Use the following steps to configure webhook: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.webhook.: | url: https:/// @@ -50,7 +50,7 @@ data: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: template.github-commit-status: | webhook: @@ -82,7 +82,7 @@ metadata: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.webhook.github: | url: https://api.github.com @@ -97,7 +97,7 @@ data: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.webhook.github: | url: https://api.github.com @@ -128,7 +128,7 @@ data: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.webhook.jenkins: | url: http:///job//build?token= @@ -145,7 +145,7 @@ type: Opaque apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.webhook.form: | url: https://form.example.com @@ -166,7 +166,7 @@ data: apiVersion: v1 kind: ConfigMap metadata: - name: + name: argocd-notifications-cm data: service.webhook.slack_webhook: | url: https://hooks.slack.com/services/xxxxx diff --git a/docs/operator-manual/server-commands/argocd-application-controller.md b/docs/operator-manual/server-commands/argocd-application-controller.md index 1d71fc6494445..f4057bf7b04cc 100644 --- a/docs/operator-manual/server-commands/argocd-application-controller.md +++ b/docs/operator-manual/server-commands/argocd-application-controller.md @@ -17,6 +17,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-resync-jitter int Maximum time period in seconds to add as a delay jitter for application resync. --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 reconciled from --as string Username to impersonate for the operation diff --git a/docs/operator-manual/server-commands/argocd-server.md b/docs/operator-manual/server-commands/argocd-server.md index 1da27d735e1cd..a72cc041299ad 100644 --- a/docs/operator-manual/server-commands/argocd-server.md +++ b/docs/operator-manual/server-commands/argocd-server.md @@ -26,6 +26,7 @@ argocd-server [flags] ``` --address string Listen on given address (default "0.0.0.0") + --api-content-types string Semicolon separated list of allowed content types for non GET api requests. Any content type is allowed if empty. (default "application/json") --app-state-cache-expiration duration Cache expiration for app state (default 1h0m0s) --application-namespaces strings List of additional namespaces where application resources can be managed in --as string Username to impersonate for the operation diff --git a/docs/operator-manual/upgrading/2.9-2.10.md b/docs/operator-manual/upgrading/2.9-2.10.md index 4cd7c379bdc81..cfb3e286649ac 100644 --- a/docs/operator-manual/upgrading/2.9-2.10.md +++ b/docs/operator-manual/upgrading/2.9-2.10.md @@ -10,3 +10,7 @@ removed. To avoid unexpected behavior, follow the [client-side to server-side resource upgrade guide](https://kubernetes.io/docs/reference/using-api/server-side-apply/#upgrading-from-client-side-apply-to-server-side-apply) before enabling `managedNamespaceMetadata` on an existing namespace. + +## Upgraded Helm Version + +Note that bundled Helm version has been upgraded from 3.13.2 to 3.14.0. diff --git a/docs/operator-manual/user-management/microsoft.md b/docs/operator-manual/user-management/microsoft.md index 33a6b3e945940..486d647fde3d0 100644 --- a/docs/operator-manual/user-management/microsoft.md +++ b/docs/operator-manual/user-management/microsoft.md @@ -1,13 +1,16 @@ # Microsoft -* [Azure AD SAML Enterprise App Auth using Dex](#azure-ad-saml-enterprise-app-auth-using-dex) -* [Azure AD App Registration Auth using OIDC](#azure-ad-app-registration-auth-using-oidc) -* [Azure AD App Registration Auth using Dex](#azure-ad-app-registration-auth-using-dex) +!!! note "" + Entra ID was formerly known as Azure AD. -## Azure AD SAML Enterprise App Auth using Dex -### Configure a new Azure AD Enterprise App +* [Entra ID SAML Enterprise App Auth using Dex](#entra-id-saml-enterprise-app-auth-using-dex) +* [Entra ID App Registration Auth using OIDC](#entra-id-app-registration-auth-using-oidc) +* [Entra ID App Registration Auth using Dex](#entra-id-app-registration-auth-using-dex) -1. From the `Azure Active Directory` > `Enterprise applications` menu, choose `+ New application` +## Entra ID SAML Enterprise App Auth using Dex +### Configure a new Entra ID Enterprise App + +1. From the `Microsoft Entra ID` > `Enterprise applications` menu, choose `+ New application` 2. Select `Non-gallery application` 3. Enter a `Name` for the application (e.g. `Argo CD`), then choose `Add` 4. Once the application is created, open it from the `Enterprise applications` menu. @@ -31,9 +34,9 @@ - *Keep a copy of the encoded output to be used in the next section.* 9. From the `Single sign-on` menu, copy the `Login URL` parameter, to be used in the next section. -### Configure Argo to use the new Azure AD Enterprise App +### Configure Argo to use the new Entra ID Enterprise App -1. Edit `argocd-cm` and add the following `dex.config` to the data section, replacing the `caData`, `my-argo-cd-url` and `my-login-url` your values from the Azure AD App: +1. Edit `argocd-cm` and add the following `dex.config` to the data section, replacing the `caData`, `my-argo-cd-url` and `my-login-url` your values from the Entra ID App: data: url: https://my-argo-cd-url @@ -56,7 +59,7 @@ groupsAttr: Group 2. Edit `argocd-rbac-cm` to configure permissions, similar to example below. - - Use Azure AD `Group IDs` for assigning roles. + - Use Entra ID `Group IDs` for assigning roles. - See [RBAC Configurations](../rbac.md) for more detailed scenarios. # example policy @@ -70,11 +73,11 @@ p, role:org-admin, repositories, delete, *, allow g, "84ce98d1-e359-4f3b-85af-985b458de3c6", role:org-admin # (azure group assigned to role) -## Azure AD App Registration Auth using OIDC -### Configure a new Azure AD App registration -#### Add a new Azure AD App registration +## Entra ID App Registration Auth using OIDC +### Configure a new Entra ID App registration +#### Add a new Entra ID App registration -1. From the `Azure Active Directory` > `App registrations` menu, choose `+ New registration` +1. From the `Microsoft Entra ID` > `App registrations` menu, choose `+ New registration` 2. Enter a `Name` for the application (e.g. `Argo CD`). 3. Specify who can use the application (e.g. `Accounts in this organizational directory only`). 4. Enter Redirect URI (optional) as follows (replacing `my-argo-cd-url` with your Argo URL), then choose `Add`. @@ -92,29 +95,29 @@ - **Redirect URI:** `http://localhost:8085/auth/callback` ![Azure App registration's Authentication](../../assets/azure-app-registration-authentication.png "Azure App registration's Authentication") -#### Add credentials a new Azure AD App registration +#### Add credentials a new Entra ID App registration 1. From the `Certificates & secrets` menu, choose `+ New client secret` 2. Enter a `Name` for the secret (e.g. `ArgoCD-SSO`). - Make sure to copy and save generated value. This is a value for the `client_secret`. ![Azure App registration's Secret](../../assets/azure-app-registration-secret.png "Azure App registration's Secret") -#### Setup permissions for Azure AD Application +#### Setup permissions for Entra ID Application 1. From the `API permissions` menu, choose `+ Add a permission` 2. Find `User.Read` permission (under `Microsoft Graph`) and grant it to the created application: - ![Azure AD API permissions](../../assets/azure-api-permissions.png "Azure AD API permissions") + ![Entra ID API permissions](../../assets/azure-api-permissions.png "Entra ID API permissions") 3. From the `Token Configuration` menu, choose `+ Add groups claim` - ![Azure AD token configuration](../../assets/azure-token-configuration.png "Azure AD token configuration") + ![Entra ID token configuration](../../assets/azure-token-configuration.png "Entra ID token configuration") -### Associate an Azure AD group to your Azure AD App registration +### Associate an Entra ID group to your Entra ID App registration -1. From the `Azure Active Directory` > `Enterprise applications` menu, search the App that you created (e.g. `Argo CD`). - - An Enterprise application with the same name of the Azure AD App registration is created when you add a new Azure AD App registration. +1. From the `Microsoft Entra ID` > `Enterprise applications` menu, search the App that you created (e.g. `Argo CD`). + - An Enterprise application with the same name of the Entra ID App registration is created when you add a new Entra ID App registration. 2. From the `Users and groups` menu of the app, add any users or groups requiring access to the service. ![Azure Enterprise SAML Users](../../assets/azure-enterprise-users.png "Azure Enterprise SAML Users") -### Configure Argo to use the new Azure AD App registration +### Configure Argo to use the new Entra ID App registration 1. Edit `argocd-cm` and configure the `data.oidc.config` and `data.url` section: @@ -173,7 +176,7 @@ Refer to [operator-manual/argocd-rbac-cm.yaml](https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/argocd-rbac-cm.yaml) for all of the available variables. -## Azure AD App Registration Auth using Dex +## Entra ID App Registration Auth using Dex Configure a new AD App Registration, as above. Then, add the `dex.config` to `argocd-cm`: @@ -200,9 +203,9 @@ data: 1. Open a new browser tab and enter your ArgoCD URI: https://`` ![Azure SSO Web Log In](../../assets/azure-sso-web-log-in-via-azure.png "Azure SSO Web Log In") -3. Click `LOGIN VIA AZURE` button to log in with your Azure Active Directory account. You’ll see the ArgoCD applications screen. +3. Click `LOGIN VIA AZURE` button to log in with your Microsoft Entra ID account. You’ll see the ArgoCD applications screen. ![Azure SSO Web Application](../../assets/azure-sso-web-application.png "Azure SSO Web Application") -4. Navigate to User Info and verify Group ID. Groups will have your group’s Object ID that you added in the `Setup permissions for Azure AD Application` step. +4. Navigate to User Info and verify Group ID. Groups will have your group’s Object ID that you added in the `Setup permissions for Entra ID Application` step. ![Azure SSO Web User Info](../../assets/azure-sso-web-user-info.png "Azure SSO Web User Info") ### Log in to ArgoCD using CLI diff --git a/docs/operator-manual/user-management/okta.md b/docs/operator-manual/user-management/okta.md index 09d7099d19954..308254759de6e 100644 --- a/docs/operator-manual/user-management/okta.md +++ b/docs/operator-manual/user-management/okta.md @@ -118,34 +118,81 @@ data: ## OIDC (without Dex) -!!! warning "Do you want groups for RBAC later?" - If you want `groups` scope returned from Okta you need to unfortunately contact support to enable [API Access Management with Okta](https://developer.okta.com/docs/concepts/api-access-management/) or [_just use SAML above!_](#saml-with-dex) +!!! warning "Okta groups for RBAC" + If you want `groups` scope returned from Okta, you will need to enable [API Access Management with Okta](https://developer.okta.com/docs/concepts/api-access-management/). This addon is free, and automatically enabled, on Okta developer edition. However, it's an optional add-on for production environments, with an additional associated cost. - Next you may need the API Access Management feature, which the support team can enable for your OktaPreview domain for testing, to enable "custom scopes" and a separate endpoint to use instead of the "public" `/oauth2/v1/authorize` API Access Management endpoint. This might be a paid feature if you want OIDC unfortunately. The free alternative I found was SAML. + You may alternately add a "groups" scope and claim to the default authorization server, and then filter the claim in the Okta application configuration. It's not clear if this requires the Authorization Server add-on. + + If this is not an option for you, use the [SAML (with Dex)](#saml-with-dex) option above instead. + +!!! note + These instructions and screenshots are of Okta version 2023.05.2 E. You can find the current version in the Okta website footer. + +First, create the OIDC integration: + +1. On the `Okta Admin` page, navigate to the Okta Applications at `Applications > Applications.` +1. Choose `Create App Integration`, and choose `OIDC`, and then `Web Application` in the resulting dialogues. + ![Okta OIDC app dialogue](../../assets/okta-create-oidc-app.png) +1. Update the following: + 1. `App Integration name` and `Logo` - set these to suit your needs; they'll be displayed in the Okta catalogue. + 1. `Sign-in redirect URLs`: Add `https://argocd.example.com/auth/callback`; replacing `argocd.example.com` with your ArgoCD web interface URL. Also add `http://localhost:8085/auth/callback` if you would like to be able to login with the CLI. + 1. `Sign-out redirect URIs`: Add `https://argocd.example.com`; substituting the correct domain name as above. + 1. Either assign groups, or choose to skip this step for now. + 1. Leave the rest of the options as-is, and save the integration. + ![Okta app settings](../../assets/okta-app.png) +1. Copy the `Client ID` and the `Client Secret` from the newly created app; you will need these later. + +Next, create a custom Authorization server: 1. On the `Okta Admin` page, navigate to the Okta API Management at `Security > API`. - ![Okta API Management](../../assets/api-management.png) -1. Choose your `default` authorization server. -1. Click `Scopes > Add Scope` - 1. Add a scope called `groups`. - ![Groups Scope](../../assets/groups-scope.png) -1. Click `Claims > Add Claim.` - 1. Add a claim called `groups` - 1. Choose the matching options you need, one example is: - * e.g. to match groups starting with `argocd-` you'd return an `ID Token` using your scope name from step 3 (e.g. `groups`) where the groups name `matches` the `regex` `argocd-.*` - ![Groups Claim](../../assets/groups-claim.png) -1. Edit the `argocd-cm` and configure the `data.oidc.config` section: +1. Click `Add Authorization Server`, and assign it a name and a description. The `Audience` should match your ArgoCD URL - `https://argocd.example.com` +1. Click `Scopes > Add Scope`: + 1. Add a scope called `groups`. Leave the rest of the options as default. + ![Groups Scope](../../assets/okta-groups-scope.png) +1. Click `Claims > Add Claim`: + 1. Add a claim called `groups`. + 1. Adjust the `Include in token type` to `ID Token`, `Always`. + 1. Adjust the `Value type` to `Groups`. + 1. Add a filter that will match the Okta groups you want passed on to ArgoCD; for example `Regex: argocd-.*`. + 1. Set `Include in` to `groups` (the scope you created above). + ![Groups Claim](../../assets/okta-groups-claim.png) +1. Click on `Access Policies` > `Add Policy.` This policy will restrict how this authorization server is used. + 1. Add a name and description. + 1. Assign the policy to the client (application integration) you created above. The field should auto-complete as you type. + 1. Create the policy. + ![Auth Policy](../../assets/okta-auth-policy.png) +1. Add a rule to the policy: + 1. Add a name; `default` is a reasonable name for this rule. + 1. Fine-tune the settings to suit your organization's security posture. Some ideas: + 1. uncheck all the grant types except the Authorization Code. + 1. Adjust the token lifetime to govern how long a session can last. + 1. Restrict refresh token lifetime, or completely disable it. + ![Default rule](../../assets/okta-auth-rule.png) +1. Finally, click `Back to Authorization Servers`, and copy the `Issuer URI`. You will need this later. + +If you haven't yet created Okta groups, and assigned them to the application integration, you should do that now: + +1. Go to `Directory > Groups` +1. For each group you wish to add: + 1. Click `Add Group`, and choose a meaningful name. It should match the regex or pattern you added to your custom `group` claim. + 1. Click on the group (refresh the page if the new group didn't show up in the list). + 1. Assign Okta users to the group. + 1. Click on `Applications` and assign the OIDC application integration you created to this group. + 1. Repeat as needed. + +Finally, configure ArgoCD itself. Edit the `argocd-cm` configmap: ```yaml +url: https://argocd.example.com oidc.config: | name: Okta - issuer: https://yourorganization.oktapreview.com - clientID: 0oaltaqg3oAIf2NOa0h3 - clientSecret: ZXF_CfUc-rtwNfzFecGquzdeJ_MxM4sGc8pDT2Tg6t + # this is the authorization server URI + issuer: https://example.okta.com/oauth2/aus9abcdefgABCDEFGd7 + clientID: 0oa9abcdefgh123AB5d7 + clientSecret: ABCDEFG1234567890abcdefg requestedScopes: ["openid", "profile", "email", "groups"] requestedIDTokenClaims: {"groups": {"essential": true}} ``` - - +You may want to store the `clientSecret` in a Kubernetes secret; see [how to deal with SSO secrets](./index.md/#sensitive-data-and-sso-client-secrets ) for more details. diff --git a/docs/proposals/decouple-application-sync-user-using-impersonation.md b/docs/proposals/decouple-application-sync-user-using-impersonation.md new file mode 100644 index 0000000000000..e7e459a7059c0 --- /dev/null +++ b/docs/proposals/decouple-application-sync-user-using-impersonation.md @@ -0,0 +1,592 @@ +--- +title: Decouple Control plane and Application Sync privileges +authors: + - "@anandf" +sponsors: + - Red Hat +reviewers: + - "@blakepettersson" + - "@crenshaw-dev" + - "@jannfis" +approvers: + - "@alexmt" + - "@crenshaw-dev" + - "@jannfis" + +creation-date: 2023-06-23 +last-updated: 2024-02-06 +--- + +# Decouple Application Sync using Impersonation + +Application syncs in Argo CD have the same privileges as the Argo CD control plane. As a consequence, in a multi-tenant setup, the Argo CD control plane privileges needs to match the tenant that needs the highest privileges. As an example, if an Argo CD instance has 10 Applications and only one of them requires admin privileges, then the Argo CD control plane must have admin privileges in order to be able to sync that one Application. Argo CD provides a multi-tenancy model to restrict what each Application can do using `AppProjects`, even though the control plane has higher privileges, however that creates a large attack surface since if Argo CD is compromised, attackers would have cluster-admin access to the cluster. + +The goal of this proposal is to perform the Application sync as a different user using impersonation and use the service account provided in the cluster config purely for control plane operations. + +### What is Impersonation + +Impersonation is a feature in Kubernetes and enabled in the `kubectl` CLI client, using which, a user can act as another user through impersonation headers. For example, an admin could use this feature to debug an authorization policy by temporarily impersonating another user and seeing if a request was denied. + +Impersonation requests first authenticate as the requesting user, then switch to the impersonated user info. + +``` +kubectl --as ... +kubectl --as --as-group ... +``` + +## Open Questions [optional] + +- Should the restrictions imposed as part of the `AppProjects` be honored if the impersonation feature is enabled ? +>Yes, other restrictions implemented by `AppProject` related to whitelisting/blacklisting resources must continue to be honoured. +- Can an Application refer to a service account with elevated privileges like say `cluster-admin`, `admin`, and service accounts used for running the ArgoCD controllers itself ? +>Yes, this is possible as long as the ArgoCD admin user explicitly allows it through the `AppProject` configuration. +- Among the destinations configured in the `AppProject`, if there are multiple matches for a given destination, which destination option should be used ? +>If there are more than one matching destination, either with a glob pattern match or an exact match, then we use the first valid match to determine the service account to be used for the sync operation. +- Can the kubernetes audit trail events capture the impersonation. +>Yes, kubernetes audit trail events capture both the actual user and the impersonating user details and hence its possible to track who executed the commands and as which user permissions using the audit trails. +- Would the Sync hooks be using the impersonation service account. +>Yes, if the impersonation feature is enabled and customers use Sync hooks, then impersonation service account would be used for executing the hook jobs as well. +- If application resources have hardcoded namespaces in the git repository, would different service accounts be used for each resource during the sync operation ? +>The service account to be used for impersonation is determined on a per Application level rather than on per resource level. The value specified in `Application.spec.destination.namespace` would be used to determine the service account to be used for the sync operation of all resources present in the `Application`. + +## Summary + +In a multi team/multi tenant environment, an application team is typically granted access to a namespace to self-manage their Applications in a declarative way. Current implementation of ArgoCD requires the ArgoCD Administrator to create an `AppProject` with access settings configured to replicate the RBAC resources that are configured for each team. This approach requires duplication of effort and also requires syncing the access between both to maintain the security posture. It would be desirable for users to use the existing RBAC rules without having to revert to Argo CD API to create and manage these Applications. One namespace per team, or even one namespace per application is what we are looking to address as part of this proposal. + +## Motivation + +This proposal would allow ArgoCD administrators to manage the cluster permissions using kubernetes native RBAC implementation rather than using complex configurations in `AppProjects` to restrict access to individual applications. By decoupling the privileges required for application sync from the privileges required for ArgoCD control plane operations, the security requirement of providing least privileges can be achieved there by improving the security posture of ArgoCD. For implementing multi team/tenant use cases, this decoupling would be greatly beneficial. + +### Assumptions + +- Namespaces are pre-populated with one or more `ServiceAccounts` that define the permissions for each `AppProject`. +- Many users prefer to control access to kubernetes resources through kubernetes RBAC constructs instead of Argo specific constructs. +- Each tenant is generally given access to a specific namespace along with a service account, role or cluster role and role binding to control access to that namespace. +- `Applications` created by a tenant manage namespaced resources. +- An `AppProject` can either be mapped to a single tenant or multiple related tenants and the respective destinations that needs to be managed via the `AppProject`, needs to be configured. + + +### Goals +- Applications may only impersonate ServiceAccounts that live in the same namespace as the destination namespace configured in the application.If the service account is created in a different namespace, then the user can provide the service account name in the format `:` . ServiceAccount to be used for syncing each application is determined by the target destination configured in the `AppProject` associated with the `Application`. +- If impersonation feature is enabled, and no service account name is provided in the associated `AppProject`, then the default service account of the destination namespace of the `Application` should be used. +- Access restrictions implemented through properties in AppProject (if done) must have the existing behavior. From a security standpoint, any restrictions that were available before switching to a service account based approach should continue to exist even when the impersonation feature is enabled. + +### Non-Goals + +None + +## Proposal + +As part of this proposal, it would be possible for an ArgoCD Admin to specify a service account name in `AppProjects` CR for a single or a group of destinations. A destination is uniquely identified by a target cluster and a namespace combined. + +When applications gets synced, based on its destination (target cluster and namespace combination), the `defaultServiceAccount` configured in the `AppProject` will be selected and used for impersonation when executing the kubectl commands for the sync operation. + +We would be introducing a new element `destinationServiceAccounts` in `AppProject.spec`. This element is used for the sole purpose of specifying the impersonation configuration. The `defaultServiceAccount` configured for the `AppProject` would be used for the sync operation for a particular destination cluster and namespace. If impersonation feature is enabled and no specific service account is provided in the `AppProject` CR, then the `default` service account in the destination namespace would be used for impersonation. + +``` +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: my-project + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + description: Example Project + # Allow manifests to deploy from any Git repos + sourceRepos: + - '*' + destinations: + - * + destinationServiceAccounts: + - server: https://kubernetes.default.svc + namespace: guestbook + defaultServiceAccount: guestbook-deployer + - server: https://kubernetes.default.svc + namespace: guestbook-dev + defaultServiceAccount: guestbook-dev-deployer + - server: https://kubernetes.default.svc + namespace: guestbook-stage + defaultServiceAccount: guestbook-stage-deployer +``` + +### Structure of DestinationServiceAccount: +|Parameter| Type | Required/Optional| Description| +| ------ | ------ | ------- | -------- | +| server | string | Required | Server specifies the URL of the target cluster's Kubernetes control plane API. Glob patterns are supported. | +| namespace | string | Required | Namespace specifies the target namespace for the application's resources. Glob patterns are supported. | +| defaultServiceAccount | string | Required| DefaultServiceAccount specifies the service account to be impersonated when performing the `Application` sync operation.| + +**Note:** Only server URL for the target cluster is supported and target cluster name is not supported. + +### Future enhancements + +In a future release, we plan to support overriding of service accounts at the application level. In that case, we would be adding an element called `allowedServiceAccounts` to `AppProject.spec.destinationServiceAccounts[*]` + +### Use cases + +#### Use case 1: + +As a user, I would like to use kubernetes security constructs to restrict user access for application sync +So that, I can provide granular permissions based on the principle of least privilege required for syncing an application. + +#### Use case 2: + +As a user, I would like to configure a common service account for all applications associated to an AppProject +So that, I can use a generic convention of naming service accounts and avoid associating the service account per application. + +### Design considerations + +- Extending the `destinations` field under `AppProjects` was an option that was considered. But since the intent of it was to restrict the destinations that an associated `Application` can use, it was not used. Also the destination fields allowed negation operator (`!`) which would complicate the service account matching logic. The decision to create a new struct under `AppProject.Spec` for specifying the service account for each destination was considered a better alternative. + +- The field name `defaultServiceAccount` was chosen instead of `serviceAccount` as we wanted to support overriding of the service account at an `Application` at a later point in time and wanted to reserve the name `serviceAccount` for future extension. + +- Not supporting all impersonation options at the moment to keep the initial design to a minimum. Based on the need and feedback, support to impersonate users or groups can be added in future. + +### Implementation Details/Notes/Constraints + +#### Component : GitOps Engine + +- Fix GitOps Engine code to honor Impersonate configuration set in the Application sync context for all kubectl commands that are being executed. + +#### Component: ArgoCD API + +- Create a new struct type `DestinationServiceAccount` having fields `namespace`, `server` and `defaultServiceAccount` +- Create a new field `DestinationServiceAccounts` under a `AppProject.Spec` that takes in a list of `DestinationServiceAccount` objects. +- Add Documentation for newly introduced struct and its fields for `DestinationServiceAccount` and `DestinationServiceAccounts` under `AppProject.Spec` + +#### Component: ArgoCD Application Controller + +- Provide a configuration in `argocd-cm` which can be modified to enable the Impersonation feature. Set `applicationcontroller.enable.impersonation: true` in the Argo CD ConfigMap. Default value of `applicationcontroller.enable.impersonation` would be `false` and user has to explicitly override it to use this feature. +- Provide an option to override the Impersonation feature using environment variables. +Set `ARGOCD_APPLICATION_CONTROLLER_ENABLE_IMPERSONATION=true` in the Application controller environment variables. Default value of the environment variable must be `false` and user has to explicitly set it to `true` to use this feature. +- Provide an option to enable this feature using a command line flag `--enable-impersonation`. This new argument option needs to be added to the Application controller args. +- Fix Application Controller `sync.go` to set the Impersonate configuration from the AppProject CR to the `SyncContext` Object (rawConfig and restConfig field, need to understand which config is used for the actual sync and if both configs need to be impersonated.) + +#### Component: ArgoCD UI + +- Provide option to create `DestinationServiceAccount` with fields `namespace`, `server` and `defaultServiceAccount`. +- Provide option to add multiple `DestinationServiceAccounts` to an `AppProject` created/updated via the web console. +- Update the User Guide documentation on how to use these newly added fields from the web console. + +#### Component: ArgoCD CLI + +- Provide option to create `DestinationServiceAccount` with fields `namespace`, `server` and `defaultServiceAccount`. +- Provide option to add multiple `DestinationServiceAccounts` to an `AppProject` created/updated via the web console. +- Update the User Guide and other documentation where the CLI option usages are explained. + +#### Component: Documentation + +- Add note that this is a Beta feature in the documentation. +- Add a separate section for this feature under user-guide section. +- Update the ArgoCD CLI command reference documentation. +- Update the ArgoCD UI command reference documentation. + +### Detailed examples + +#### Example 1: Service account for application sync specified at the AppProject level for all namespaces + +In this specific scenario, service account name `generic-deployer` will get used for the application sync as the namespace `guestbook` matches the glob pattern `*`. + +- Install ArgoCD in the `argocd` namespace. +``` +kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-cd/master/manifests/install.yaml -n argocd +``` + +- Enable the impersonation feature in ArgoCD. +``` +kubectl set env statefulset/argocd-application-controller ARGOCD_APPLICATION_CONTROLLER_ENABLE_IMPERSONATION=true +``` + +- Create a namespace called `guestbook` and a service account called `guestbook-deployer`. +``` +kubectl create namespace guestbook +kubectl create serviceaccount guestbook-deployer +``` + +- Create Role and RoleBindings and configure RBAC access for creating `Service` and `Deployment` objects in namespace `guestbook` for service account `guestbook-deployer`. +``` +kubectl create role guestbook-deployer-role --verb get,list,update,delete --resource pods,deployment,service +kubectl create rolebinding guestbook-deployer-rb --serviceaccount guestbook-deployer --role guestbook-deployer-role +``` + +- Create the `Application` in the `argocd` namespace and the required `AppProject` as below +``` +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: guestbook + namespace: argocd +spec: + project: my-project + source: + repoURL: https://github.com/argoproj/argocd-example-apps.git + targetRevision: HEAD + path: guestbook + destination: + server: https://kubernetes.default.svc + namespace: guestbook +--- +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: my-project + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + description: Example Project + # Allow manifests to deploy from any Git repos + sourceRepos: + - '*' + destinations: + - namespace: * + server: https://kubernetes.default.svc + destinationServiceAccounts: + - namespace: * + server: https://kubernetes.default.svc + defaultServiceAccount: generic-deployer +``` + +#### Example 2: Service account for application sync specified at the AppProject level for specific namespaces + +In this specific scenario, service account name `guestbook-deployer` will get used for the application sync as the namespace `guestbook` matches the target namespace `guestbook`. + +- Install ArgoCD in the `argocd` namespace. +``` +kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-cd/master/manifests/install.yaml -n argocd +``` + +- Enable the impersonation feature in ArgoCD. +``` +kubectl set env statefulset/argocd-application-controller ARGOCD_APPLICATION_CONTROLLER_ENABLE_IMPERSONATION=true +``` + +- Create a namespace called `guestbook` and a service account called `guestbook-deployer`. +``` +kubectl create namespace guestbook +kubectl create serviceaccount guestbook-deployer +``` +- Create Role and RoleBindings and configure RBAC access for creating `Service` and `Deployment` objects in namespace `guestbook` for service account `guestbook-deployer`. +``` +kubectl create role guestbook-deployer-role --verb get,list,update,delete --resource pods,deployment,service +kubectl create rolebinding guestbook-deployer-rb --serviceaccount guestbook-deployer --role guestbook-deployer-role +``` + +In this specific scenario, service account name `guestbook-deployer` will get used as it matches to the specific namespace `guestbook`. +``` +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: guestbook + namespace: argocd +spec: + project: my-project + source: + repoURL: https://github.com/argoproj/argocd-example-apps.git + targetRevision: HEAD + path: guestbook + destination: + server: https://kubernetes.default.svc + namespace: guestbook +--- +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: my-project + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + description: Example Project + # Allow manifests to deploy from any Git repos + sourceRepos: + - '*' + destinations: + - namespace: guestbook + server: https://kubernetes.default.svc + - namespace: guestbook-ui + server: https://kubernetes.default.svc + destinationServiceAccounts: + - namespace: guestbook + server: https://kubernetes.default.svc + defaultServiceAccount: guestbook-deployer + - namespace: guestbook-ui + server: https://kubernetes.default.svc + defaultServiceAccount: guestbook-ui-deployer +``` + +#### Example 3: Remote destination with cluster-admin access and using different service account for the sync operation + +**Note**: In this example, we are relying on the default service account `argocd-manager` with `cluster-admin` privileges which gets created when adding a remote cluster destination using the ArgoCD CLI. + +- Install ArgoCD in the `argocd` namespace. +``` +kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-cd/master/manifests/install.yaml -n argocd +``` + +- Enable the impersonation feature in ArgoCD. +``` +kubectl set env statefulset/argocd-application-controller ARGOCD_APPLICATION_CONTROLLER_ENABLE_IMPERSONATION=true +``` + +- Add the remote cluster as a destination to argocd +``` +argocd cluster add remote-cluster --name remote-cluster +``` +**Note:** The above command would create a service account named `argocd-manager` in `kube-system` namespace and `ClusterRole` named `argocd-manager-role` with full cluster admin access and a `ClusterRoleBinding` named `argocd-manager-role-binding` mapping the `argocd-manager-role` to the service account `remote-cluster` + +- In the remote cluster, create a namespace called `guestbook` and a service account called `guestbook-deployer`. +``` +kubectl ctx remote-cluster +kubectl create namespace guestbook +kubectl create serviceaccount guestbook-deployer +``` + +- In the remote cluster, create `Role` and `RoleBindings` and configure RBAC access for creating `Service` and `Deployment` objects in namespace `guestbook` for service account `guestbook-deployer`. + +``` +kubectl ctx remote-cluster +kubectl create role guestbook-deployer-role --verb get,list,update,delete --resource pods,deployment,service +kubectl create rolebinding guestbook-deployer-rb --serviceaccount guestbook-deployer --role guestbook-deployer-role +``` + +- Create the `Application` and `AppProject` for the `guestbook` application. +``` +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: guestbook + namespace: argocd +spec: + project: my-project + source: + repoURL: https://github.com/argoproj/argocd-example-apps.git + targetRevision: HEAD + path: guestbook + destination: + server: https://kubernetes.default.svc + namespace: guestbook +--- +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: my-project + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + description: Example Project + # Allow manifests to deploy from any Git repos + sourceRepos: + - '*' + destinations: + - namespace: guestbook + server: https://kubernetes.default.svc + serviceAccountName: guestbook-deployer + destinationServiceAccounts: + - namespace: guestbook + server: https://kubernetes.default.svc + defaultServiceAccount: guestbook-deployer +``` + +#### Example 4: Remote destination with a custom service account for the sync operation + +**Note**: In this example, we are relying on a non default service account `guestbook` created in the target cluster and namespace for the sync operation. This use case is for handling scenarios where the remote cluster is managed by a different administrator and providing a service account with `cluster-admin` level access is not feasible. + +- Install ArgoCD in the `argocd` namespace. +``` +kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-cd/master/manifests/install.yaml -n argocd +``` + +- Enable the impersonation feature in ArgoCD. +``` +kubectl set env statefulset/argocd-application-controller ARGOCD_APPLICATION_CONTROLLER_ENABLE_IMPERSONATION=true +``` + +- In the remote cluster, create a service account called `argocd-admin` +``` +kubectl ctx remote-cluster +kubectl create serviceaccount argocd-admin +kubectl create clusterrole argocd-admin-role --verb=impersonate --resource="users,groups,serviceaccounts" +kubectl create clusterrole argocd-admin-role-access-review --verb=create --resource="selfsubjectaccessreviews" +kubectl create clusterrolebinding argocd-admin-role-binding --serviceaccount argocd-admin --clusterrole argocd-admin-role +kubectl create clusterrolebinding argocd-admin-access-review-role-binding --serviceaccount argocd-admin --clusterrole argocd-admin-role +``` + +- In the remote cluster, create a namespace called `guestbook` and a service account called `guestbook-deployer`. +``` +kubectl ctx remote-cluster +kubectl create namespace guestbook +kubectl create serviceaccount guestbook-deployer +``` + +- In the remote cluster, create `Role` and `RoleBindings` and configure RBAC access for creating `Service` and `Deployment` objects in namespace `guestbook` for service account `guestbook-deployer`. +``` +kubectl create role guestbook-deployer-role --verb get,list,update,delete --resource pods,deployment,service +kubectl create rolebinding guestbook-deployer-rb --serviceaccount guestbook-deployer --role guestbook-deployer-role +``` + +In this specific scenario, service account name `guestbook-deployer` will get used as it matches to the specific namespace `guestbook`. +``` +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: guestbook + namespace: argocd +spec: + project: my-project + source: + repoURL: https://github.com/argoproj/argocd-example-apps.git + targetRevision: HEAD + path: guestbook + destination: + server: https://kubernetes.default.svc + namespace: guestbook +--- +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: my-project + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + description: Example Project + # Allow manifests to deploy from any Git repos + sourceRepos: + - '*' + destinations: + - namespace: guestbook + server: https://kubernetes.default.svc + - namespace: guestbook-ui + server: https://kubernetes.default.svc + destinationServiceAccounts: + - namespace: guestbook + server: https://kubernetes.default.svc + defaultServiceAccount: guestbook-deployer + - namespace: guestbook-ui + server: https://kubernetes.default.svc + defaultServiceAccount: guestbook-ui-deployer +``` + +### Special cases + +#### Specifying service account in a different namespace + +By default, the service account would be looked up in the Application's destination namespace configured through `Application.Spec.Destination.Namespace` field. If the service account is in a different namespace, then users can provide the namespace of the service account explicitly in the format : +eg: +``` + ... + destinationServiceAccounts: + - server: https://kubernetes.default.svc + namespace: * + defaultServiceAccount: mynamespace:guestbook-deployer + ... +``` + +#### Multiple matches of destinations + +If there are multiple matches for a given destination, the first valid match in the list of `destinationServiceAccounts` would be used. + +eg: +Lets assume that the `AppProject` has the below `destinationServiceAccounts` configured. +``` + ... + destinationServiceAccounts: + - server: https://kubernetes.default.svc + namespace: guestbook-prod + defaultServiceAccount: guestbook-prod-deployer + - server: https://kubernetes.default.svc + namespace: guestbook-* + defaultServiceAccount: guestbook-generic-deployer + - server: https://kubernetes.default.svc + namespace: * + defaultServiceAccount: generic-deployer + ... +``` +- If the application destination namespace is `myns`, then the service account `generic-deployer` would be used as the first valid match is the glob pattern `*` and there are no other valid matches in the list. +- If the application destination namespace is `guestbook-dev` or `guestbook-stage`, then both glob patterns `*` and `guestbook-*` are valid matches, however `guestbook-*` pattern appears first and hence, the service account `guestbook-generic-deployer` would be used for the impersonation. +- If the application destination namespace is `guestbook-prod`, then there are three candidates, however the first valid match in the list is the one with service account `guestbook-prod-deployer` and that would be used for the impersonation. + +#### Application resources referring to multiple namespaces +If application resources have hardcoded namespaces in the git repository, would different service accounts be used for each resource during the sync operation ? + +The service account to be used for impersonation is determined on a per Application level rather than on per resource level. The value specified in `Application.spec.destination.namespace` would be used to determine the service account to be used for the sync operation of all resources present in the `Application`. + +### Security Considerations + +* How does this proposal impact the security aspects of Argo CD workloads ? +* Are there any unresolved follow-ups that need to be done to make the enhancement more robust ? + +### Risks and Mitigations + +#### Privilege Escalation + +There could be an issue of privilege escalation, if we allow users to impersonate without restrictions. This is mitigated by only allowing admin users to configure service account used for the sync operation at the `AppProject` level. + +Instead of allowing users to impersonate all possible users, administrators can restrict the users a particular service account can impersonate using the `resourceNames` field in the RBAC spec. + + +### Upgrade / Downgrade Strategy + +If applicable, how will the component be upgraded and downgraded? Make sure this is in the test +plan. + +Consider the following in developing an upgrade/downgrade strategy for this enhancement: + +- What changes (in invocations, configurations, API use, etc.) is an existing cluster required to + make on upgrade in order to keep previous behavior? +- What changes (in invocations, configurations, API use, etc.) is an existing cluster required to + make on upgrade in order to make use of the enhancement? + +- This feature would be implemented on an `opt-in` based on a feature flag and disabled by default. +- The new struct being added to `AppProject.Spec` would be introduced as an optional field and would be enabled only if the feature is enabled explicitly by a feature flag. If new property is used in the CR, but the feature flag is not enabled, then a warning message would be displayed during reconciliation of such CRs. + + +## Drawbacks + +- When using this feature, there is an overhead in creating namespaces, service accounts and the required RBAC policies and mapping the service accounts with the corresponding `AppProject` configuration. + +## Alternatives + +### Option 1 +Allow all options available in the `ImpersonationConfig` available to the user through the `AppProject` CRs. + +``` +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: my-project + namespace: argocd +spec: + description: Example Project + # Allow manifests to deploy from any Git repos + sourceRepos: + - '*' + destinations: + - namespace: * + server: https://kubernetes.default.svc + namespace: guestbook + impersonate: + user: system:serviceaccount:dev_ns:admin + uid: 1234 + groups: + - admin + - view + - edit +``` + +### Related issue + +https://github.com/argoproj/argo-cd/issues/7689 + + +### Related links + +https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation + +### Prior art + +https://github.com/argoproj/argo-cd/pull/3377 +https://github.com/argoproj/argo-cd/pull/7651 \ No newline at end of file diff --git a/docs/proposals/native-oci-support.md b/docs/proposals/native-oci-support.md index 64918fde8904e..7ec0053729c2e 100644 --- a/docs/proposals/native-oci-support.md +++ b/docs/proposals/native-oci-support.md @@ -126,10 +126,10 @@ Consider the following in developing an upgrade/downgrade strategy for this enha ## Drawbacks -* Sourcing content from an OCI registry may be perceived to be against GitOps principles as content is not sourced from a Git repository. This concern could be mitigated by attaching additional details related to the content (such as original Git source [URL, revision]). Though it should be noted that the GitOps principles only require a source of truth to be visioned and immutable which OCI registires support. +* Sourcing content from an OCI registry may be perceived to be against GitOps principles as content is not sourced from a Git repository. This concern could be mitigated by attaching additional details related to the content (such as original Git source [URL, revision]). Though it should be noted that the GitOps principles only require a source of truth to be visioned and immutable which OCI registries support. ## Alternatives ### Config Management Plugin -Content stored within OCI artifacts could be sourced using a Config Management Plugin which would not require changes to the core capabilities provided by Argo CD. However, this would be hacky and not represent itself within the Argo CD UI. \ No newline at end of file +Content stored within OCI artifacts could be sourced using a Config Management Plugin which would not require changes to the core capabilities provided by Argo CD. However, this would be hacky and not represent itself within the Argo CD UI. diff --git a/docs/user-guide/commands/argocd_admin_cluster.md b/docs/user-guide/commands/argocd_admin_cluster.md index bad60a0dd32bf..544c0de08959c 100644 --- a/docs/user-guide/commands/argocd_admin_cluster.md +++ b/docs/user-guide/commands/argocd_admin_cluster.md @@ -62,6 +62,6 @@ argocd admin cluster namespaces my-cluster * [argocd admin cluster generate-spec](argocd_admin_cluster_generate-spec.md) - Generate declarative config for a cluster * [argocd admin cluster kubeconfig](argocd_admin_cluster_kubeconfig.md) - Generates kubeconfig for the specified cluster * [argocd admin cluster namespaces](argocd_admin_cluster_namespaces.md) - Print information namespaces which Argo CD manages in each cluster. -* [argocd admin cluster shards](argocd_admin_cluster_shards.md) - Print information about each controller shard and portion of Kubernetes resources it is responsible for. +* [argocd admin cluster shards](argocd_admin_cluster_shards.md) - Print information about each controller shard and the estimated portion of Kubernetes resources it is responsible for. * [argocd admin cluster stats](argocd_admin_cluster_stats.md) - Prints information cluster statistics and inferred shard number diff --git a/docs/user-guide/commands/argocd_admin_cluster_generate-spec.md b/docs/user-guide/commands/argocd_admin_cluster_generate-spec.md index cc24418b023f8..79f88233fab32 100644 --- a/docs/user-guide/commands/argocd_admin_cluster_generate-spec.md +++ b/docs/user-guide/commands/argocd_admin_cluster_generate-spec.md @@ -13,6 +13,7 @@ argocd admin cluster generate-spec CONTEXT [flags] ``` --annotation stringArray Set metadata annotations (e.g. --annotation key=value) --aws-cluster-name string AWS Cluster name if set then aws cli eks token command will be used to access cluster + --aws-profile string Optional AWS profile. If set then AWS IAM Authenticator uses this profile to perform cluster operations instead of the default AWS credential provider chain. --aws-role-arn string Optional AWS role arn. If set then AWS IAM Authenticator assumes a role to perform cluster operations instead of the default AWS credential provider chain. --bearer-token string Authentication token that should be used to access K8S API server --cluster-endpoint string Cluster endpoint to use. Can be one of the following: 'kubeconfig', 'kube-public', or 'internal'. diff --git a/docs/user-guide/commands/argocd_admin_cluster_shards.md b/docs/user-guide/commands/argocd_admin_cluster_shards.md index 6648b91b2199e..48f6138d47b4a 100644 --- a/docs/user-guide/commands/argocd_admin_cluster_shards.md +++ b/docs/user-guide/commands/argocd_admin_cluster_shards.md @@ -2,7 +2,7 @@ ## argocd admin cluster shards -Print information about each controller shard and portion of Kubernetes resources it is responsible for. +Print information about each controller shard and the estimated portion of Kubernetes resources it is responsible for. ``` argocd admin cluster shards [flags] @@ -43,6 +43,7 @@ argocd admin cluster shards [flags] --sentinelmaster string Redis sentinel master group name. (default "master") --server string The address and port of the Kubernetes API server --shard int Cluster shard filter (default -1) + --sharding-method string Sharding method. Defaults: legacy. Supported sharding methods are : [legacy, round-robin] (default "legacy") --tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used. --token string Bearer token for authentication to the API server --user string The name of the kubeconfig user to use diff --git a/docs/user-guide/commands/argocd_admin_cluster_stats.md b/docs/user-guide/commands/argocd_admin_cluster_stats.md index 960fd12caaef1..c5297ce7e35ed 100644 --- a/docs/user-guide/commands/argocd_admin_cluster_stats.md +++ b/docs/user-guide/commands/argocd_admin_cluster_stats.md @@ -57,6 +57,7 @@ argocd admin cluster stats target-cluster --sentinelmaster string Redis sentinel master group name. (default "master") --server string The address and port of the Kubernetes API server --shard int Cluster shard filter (default -1) + --sharding-method string Sharding method. Defaults: legacy. Supported sharding methods are : [legacy, round-robin] (default "legacy") --tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used. --token string Bearer token for authentication to the API server --user string The name of the kubeconfig user to use diff --git a/docs/user-guide/commands/argocd_cluster_add.md b/docs/user-guide/commands/argocd_cluster_add.md index 6d3a094b4bf83..8a80a12f5a4d5 100644 --- a/docs/user-guide/commands/argocd_cluster_add.md +++ b/docs/user-guide/commands/argocd_cluster_add.md @@ -13,6 +13,7 @@ argocd cluster add CONTEXT [flags] ``` --annotation stringArray Set metadata annotations (e.g. --annotation key=value) --aws-cluster-name string AWS Cluster name if set then aws cli eks token command will be used to access cluster + --aws-profile string Optional AWS profile. If set then AWS IAM Authenticator uses this profile to perform cluster operations instead of the default AWS credential provider chain. --aws-role-arn string Optional AWS role arn. If set then AWS IAM Authenticator assumes a role to perform cluster operations instead of the default AWS credential provider chain. --cluster-endpoint string Cluster endpoint to use. Can be one of the following: 'kubeconfig', 'kube-public', or 'internal'. --cluster-resources Indicates if cluster level resources should be managed. The setting is used only if list of managed namespaces is not empty. diff --git a/docs/user-guide/helm.md b/docs/user-guide/helm.md index 866f9c6d935aa..ae6422f46382a 100644 --- a/docs/user-guide/helm.md +++ b/docs/user-guide/helm.md @@ -25,6 +25,23 @@ spec: namespace: kubeseal ``` +Another example using a public OCI helm chart: +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: nginx +spec: + project: default + source: + chart: nginx + repoURL: registry-1.docker.io/bitnamicharts # note: the oci:// syntax is not included. + targetRevision: 15.9.0 + destination: + name: "in-cluster" + namespace: nginx +``` + !!! note "When using multiple ways to provide values" Order of precedence is `parameters > valuesObject > values > valueFiles > helm repository values.yaml` (see [Here](./helm.md#helm-value-precedence) for a more detailed example) diff --git a/docs/user-guide/kustomize.md b/docs/user-guide/kustomize.md index 647e753649cce..3da35b7eede76 100644 --- a/docs/user-guide/kustomize.md +++ b/docs/user-guide/kustomize.md @@ -106,6 +106,37 @@ spec: namespace: default ``` +## Components +Kustomize [components](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/components.md) encapsulate both resources and patches together. They provide a powerful way to modularize and reuse configuration in Kubernetes applications. + +Outside of Argo CD, to utilize components, you must add the following to the `kustomization.yaml` that the Application references. For example: +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +... +components: +- ../component +``` + +With support added for components in `v2.10.0`, you can now reference a component directly in the Application: +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: application-kustomize-components +spec: + ... + source: + path: examples/application-kustomize-components/base + repoURL: https://github.com/my-user/my-repo + targetRevision: main + + # This! + kustomize: + components: + - ../component # relative to the kustomization.yaml (`source.path`). +``` + ## Private Remote Bases If you have remote bases that are either (a) HTTPS and need username/password (b) SSH and need SSH private key, then they'll inherit that from the app's repo. diff --git a/docs/user-guide/resource_hooks.md b/docs/user-guide/resource_hooks.md index a6fdaf8bd2e05..6e15a55bb20c2 100644 --- a/docs/user-guide/resource_hooks.md +++ b/docs/user-guide/resource_hooks.md @@ -8,9 +8,9 @@ and after a Sync operation. Hooks can also be run if a Sync operation fails at a * Using a `Sync` hook to orchestrate a complex deployment requiring more sophistication than the Kubernetes rolling update strategy. * Using a `PostSync` hook to run integration and health checks after a deployment. -* Using a `SyncFail` hook to run clean-up or finalizer logic if a Sync operation fails. _`SyncFail` hooks are only available starting in v1.2_ -* Using a `PostDelete` hook to run clean-up or finalizer logic after an all Application resources are deleted. Please note that - `PostDelete` hooks are only deleted if delete policy matches to the aggregated deletion hooks status and not garbage collected after the application is deleted. +* Using a `SyncFail` hook to run clean-up or finalizer logic if a Sync operation fails. +* Using a `PostDelete` hook to run clean-up or finalizer logic after all Application resources are deleted. Please note that + `PostDelete` hooks are only deleted if the delete policy matches the aggregated deletion hooks status and not garbage collected after the application is deleted. ## Usage @@ -39,7 +39,8 @@ The following hooks are defined: | `Sync` | Executes after all `PreSync` hooks completed and were successful, at the same time as the application of the manifests. | | `Skip` | Indicates to Argo CD to skip the application of the manifest. | | `PostSync` | Executes after all `Sync` hooks completed and were successful, a successful application, and all resources in a `Healthy` state. | -| `SyncFail` | Executes when the sync operation fails. _Available starting in v1.2_ | +| `SyncFail` | Executes when the sync operation fails. | +| `PostDelete` | Executes after all Application resources are deleted. _Available starting in v2.10._ | ### Generate Name diff --git a/docs/user-guide/sync-options.md b/docs/user-guide/sync-options.md index e5b1fe55e8e66..985f9fcf3c974 100644 --- a/docs/user-guide/sync-options.md +++ b/docs/user-guide/sync-options.md @@ -270,7 +270,7 @@ spec: - RespectIgnoreDifferences=true ``` -The example above shows how an Argo CD Application can be configured so it will ignore the `spec.replicas` field from the desired state (git) during the sync stage. This is achieve by calculating and pre-patching the desired state before applying it in the cluster. Note that the `RespectIgnoreDifferences` sync option is only effective when the resource is already created in the cluster. If the Application is being created and no live state exists, the desired state is applied as-is. +The example above shows how an Argo CD Application can be configured so it will ignore the `spec.replicas` field from the desired state (git) during the sync stage. This is achieved by calculating and pre-patching the desired state before applying it in the cluster. Note that the `RespectIgnoreDifferences` sync option is only effective when the resource is already created in the cluster. If the Application is being created and no live state exists, the desired state is applied as-is. ## Create Namespace diff --git a/docs/user-guide/sync-waves.md b/docs/user-guide/sync-waves.md index 932ba396d68d2..8b17237c87571 100644 --- a/docs/user-guide/sync-waves.md +++ b/docs/user-guide/sync-waves.md @@ -37,7 +37,7 @@ Hooks and resources are assigned to wave zero by default. The wave can be negati When Argo CD starts a sync, it orders the resources in the following precedence: * The phase -* The wave they are in (lower values first) +* The wave they are in (lower values first for creation & updation and higher values first for deletion) * By kind (e.g. [namespaces first and then other Kubernetes resources, followed by custom resources](https://github.com/argoproj/gitops-engine/blob/bc9ce5764fa306f58cf59199a94f6c968c775a2d/pkg/sync/sync_tasks.go#L27-L66)) * By name @@ -49,6 +49,8 @@ It repeats this process until all phases and waves are in-sync and healthy. Because an application can have resources that are unhealthy in the first wave, it may be that the app can never get to healthy. +During pruning of resources, resources from higher waves are processed first before moving to lower waves. If, for any reason, a resource isn't removed/pruned in a wave, the resources in next waves won't be processed. This is to ensure proper resource cleanup between waves. + Note that there's currently a delay between each sync wave in order give other controllers a chance to react to the spec change that we just applied. This also prevent Argo CD from assessing resource health too quickly (against the stale object), causing hooks to fire prematurely. The current delay between each sync wave is 2 seconds and can be configured via environment diff --git a/go.mod b/go.mod index 07dd99e4beff1..cb024e3183404 100644 --- a/go.mod +++ b/go.mod @@ -13,10 +13,10 @@ require ( github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d github.com/alicebob/miniredis/v2 v2.30.4 github.com/antonmedv/expr v1.15.2 - github.com/argoproj/gitops-engine v0.7.1-0.20231218194513-aba38192fb16 - github.com/argoproj/notifications-engine v0.4.1-0.20231027194313-a8d185ecc0a9 + github.com/argoproj/gitops-engine v0.7.1-0.20240124052710-5fd9f449e757 + github.com/argoproj/notifications-engine v0.4.1-0.20240206192038-2daee6022f41 github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1 - github.com/aws/aws-sdk-go v1.44.317 + github.com/aws/aws-sdk-go v1.50.8 github.com/bmatcuk/doublestar/v4 v4.6.0 github.com/bombsimon/logrusr/v2 v2.0.1 github.com/bradleyfalzon/ghinstallation/v2 v2.6.0 @@ -25,10 +25,12 @@ require ( github.com/coreos/go-oidc/v3 v3.6.0 github.com/cyphar/filepath-securejoin v0.2.4 github.com/dustin/go-humanize v1.0.1 - github.com/evanphx/json-patch v5.6.0+incompatible + github.com/evanphx/json-patch v5.9.0+incompatible + github.com/felixge/httpsnoop v1.0.3 github.com/fsnotify/fsnotify v1.6.0 github.com/gfleury/go-bitbucket-v1 v0.0.0-20220301131131-8e7ed04b843e - github.com/go-git/go-git/v5 v5.10.1 + github.com/go-git/go-git/v5 v5.11.0 + github.com/go-jose/go-jose/v3 v3.0.1 github.com/go-logr/logr v1.3.0 github.com/go-openapi/loads v0.21.2 github.com/go-openapi/runtime v0.26.0 @@ -88,7 +90,6 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d google.golang.org/grpc v1.59.0 google.golang.org/protobuf v1.31.0 - gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.26.11 @@ -114,19 +115,20 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v0.5.2 // indirect - github.com/aws/aws-sdk-go-v2 v1.17.3 // indirect - github.com/aws/aws-sdk-go-v2/config v1.18.8 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.8 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 // indirect - github.com/aws/aws-sdk-go-v2/service/sqs v1.20.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.18.0 // indirect - github.com/aws/smithy-go v1.13.5 // indirect + github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect + github.com/aws/aws-sdk-go-v2/config v1.25.12 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect + github.com/aws/aws-sdk-go-v2/service/sqs v1.29.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect + github.com/aws/smithy-go v1.19.0 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/google/s2a-go v0.1.4 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect @@ -178,13 +180,11 @@ require ( github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fatih/camelcase v1.0.0 // indirect - github.com/felixge/httpsnoop v1.0.3 // indirect github.com/fvbommel/sortorder v1.0.1 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect - github.com/go-jose/go-jose/v3 v3.0.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.21.4 // indirect github.com/go-openapi/errors v0.20.3 // indirect @@ -242,7 +242,7 @@ require ( github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/client_model v0.3.0 github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect github.com/rivo/uniseg v0.4.4 // indirect @@ -267,7 +267,7 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.18.0 + golang.org/x/net v0.19.0 golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 diff --git a/go.sum b/go.sum index 0c5e889f6bdf6..2d33e5a248cce 100644 --- a/go.sum +++ b/go.sum @@ -694,10 +694,10 @@ github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/appscode/go v0.0.0-20191119085241-0887d8ec2ecc/go.mod h1:OawnOmAL4ZX3YaPdN+8HTNwBveT1jMsqP74moa9XUbE= -github.com/argoproj/gitops-engine v0.7.1-0.20231218194513-aba38192fb16 h1:kR15L8UsSVr7oitABKU88msQirMT0/RO/KRla1jkq/s= -github.com/argoproj/gitops-engine v0.7.1-0.20231218194513-aba38192fb16/go.mod h1:gWE8uROi7hIkWGNAVM+8FWkMfo0vZ03SLx/aFw/DBzg= -github.com/argoproj/notifications-engine v0.4.1-0.20231027194313-a8d185ecc0a9 h1:1lt0VXzmLK7Vv0kaeal3S6/JIfzPyBORkUWXhiqF3l0= -github.com/argoproj/notifications-engine v0.4.1-0.20231027194313-a8d185ecc0a9/go.mod h1:E/vv4+by868m0mmflaRfGBmKBtAupoF+mmyfekP8QCk= +github.com/argoproj/gitops-engine v0.7.1-0.20240124052710-5fd9f449e757 h1:5fKAhTQcTBom0vin56cz/UTPx2GMuvdb+lJRAUOPbHA= +github.com/argoproj/gitops-engine v0.7.1-0.20240124052710-5fd9f449e757/go.mod h1:gWE8uROi7hIkWGNAVM+8FWkMfo0vZ03SLx/aFw/DBzg= +github.com/argoproj/notifications-engine v0.4.1-0.20240206192038-2daee6022f41 h1:PQE8LbcbRHdtnQzeEWwVU2QHXACKOA30yS3No5HSoTQ= +github.com/argoproj/notifications-engine v0.4.1-0.20240206192038-2daee6022f41/go.mod h1:TsyusmXQWIL0ST7YMRG/ered7WlWDmbmnPpXnS2LJmM= github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1 h1:qsHwwOJ21K2Ao0xPju1sNuqphyMnMYkyB3ZLoLtxWpo= github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1/go.mod h1:CZHlkyAD1/+FbEn6cB2DQTj48IoLGvEYsWEvtzP3238= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -713,35 +713,37 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.44.289/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= -github.com/aws/aws-sdk-go v1.44.317 h1:+8XWrLmGMwPPXSRSLPzhgcGnzJ2mYkgkrcB9C/GnSOU= -github.com/aws/aws-sdk-go v1.44.317/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.50.8 h1:gY0WoOW+/Wz6XmYSgDH9ge3wnAevYDSQWPxxJvqAkP4= +github.com/aws/aws-sdk-go v1.50.8/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/aws/aws-sdk-go-v2 v1.17.3 h1:shN7NlnVzvDUgPQ+1rLMSxY8OWRNDRYtiqe0p/PgrhY= -github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2/config v1.18.8 h1:lDpy0WM8AHsywOnVrOHaSMfpaiV2igOw8D7svkFkXVA= -github.com/aws/aws-sdk-go-v2/config v1.18.8/go.mod h1:5XCmmyutmzzgkpk/6NYTjeWb6lgo9N170m1j6pQkIBs= -github.com/aws/aws-sdk-go-v2/credentials v1.13.8 h1:vTrwTvv5qAwjWIGhZDSBH/oQHuIQjGmD232k01FUh6A= -github.com/aws/aws-sdk-go-v2/credentials v1.13.8/go.mod h1:lVa4OHbvgjVot4gmh1uouF1ubgexSCN92P6CJQpT0t8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 h1:j9wi1kQ8b+e0FBVHxCqCGo4kxDU175hoDHcWAi0sauU= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21/go.mod h1:ugwW57Z5Z48bpvUyZuaPy4Kv+vEfJWnIrky7RmkBvJg= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 h1:I3cakv2Uy1vNmmhRQmFptYDxOvBnwCdNwyw63N0RaRU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27/go.mod h1:a1/UpzeyBBerajpnP5nGZa9mGzsBn5cOKxm6NWQsvoI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 h1:5NbbMrIzmUn/TXFqAle6mgrH5m9cOvMLRGL7pnG8tRE= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21/go.mod h1:+Gxn8jYn5k9ebfHEqlhrMirFjSW0v0C9fI+KN5vk2kE= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 h1:KeTxcGdNnQudb46oOl4d90f2I33DF/c6q3RnZAmvQdQ= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28/go.mod h1:yRZVr/iT0AqyHeep00SZ4YfBAKojXz08w3XMBscdi0c= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 h1:5C6XgTViSb0bunmU57b3CT+MhxULqHH2721FVA+/kDM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21/go.mod h1:lRToEJsn+DRA9lW4O9L9+/3hjTkUzlzyzHqn8MTds5k= -github.com/aws/aws-sdk-go-v2/service/sqs v1.20.0 h1:tQoMg8i4nFAB70cJ4wiAYEiZRYo2P6uDmU2D6ys/igo= -github.com/aws/aws-sdk-go-v2/service/sqs v1.20.0/go.mod h1:jQhN5f4p3PALMNlUtfb/0wGIFlV7vGtJlPDVfxfNfPY= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.0 h1:/2gzjhQowRLarkkBOGPXSRnb8sQ2RVsjdG1C/UliK/c= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.0/go.mod h1:wo/B7uUm/7zw/dWhBJ4FXuw1sySU5lyIhVg1Bu2yL9A= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0 h1:Jfly6mRxk2ZOSlbCvZfKNS7TukSx1mIzhSsqZ/IGSZI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0/go.mod h1:TZSH7xLO7+phDtViY/KUp9WGCJMQkLJ/VpgkTFd5gh8= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.0 h1:kOO++CYo50RcTFISESluhWEi5Prhg+gaSs4whWabiZU= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.0/go.mod h1:+lGbb3+1ugwKrNTWcf2RT05Xmp543B06zDFTwiTLp7I= -github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= -github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= +github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= +github.com/aws/aws-sdk-go-v2/config v1.25.12 h1:mF4cMuNh/2G+d19nWnm1vJ/ak0qK6SbqF0KtSX9pxu0= +github.com/aws/aws-sdk-go-v2/config v1.25.12/go.mod h1:lOvvqtZP9p29GIjOTuA/76HiVk0c/s8qRcFRq2+E2uc= +github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= +github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 h1:uR9lXYjdPX0xY+NhvaJ4dD8rpSRz5VY81ccIIoNG+lw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= +github.com/aws/aws-sdk-go-v2/service/sqs v1.29.7 h1:tRNrFDGRm81e6nTX5Q4CFblea99eAfm0dxXazGpLceU= +github.com/aws/aws-sdk-go-v2/service/sqs v1.29.7/go.mod h1:8GWUDux5Z2h6z2efAtr54RdHXtLm8sq7Rg85ZNY/CZM= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= +github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= +github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -875,8 +877,8 @@ github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBF github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= -github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= @@ -927,8 +929,8 @@ github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+ github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.10.1 h1:tu8/D8i+TWxgKpzQ3Vc43e+kkhXqtsZCKI/egajKnxk= -github.com/go-git/go-git/v5 v5.10.1/go.mod h1:uEuHjxkHap8kAl//V5F/nNWwqIYtP/402ddd05mp0wg= +github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= +github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -1960,8 +1962,8 @@ golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2597,8 +2599,6 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/retry.v1 v1.0.3 h1:a9CArYczAVv6Qs6VGoLMio99GEs7kY9UzSF9+LD+iGs= gopkg.in/retry.v1 v1.0.3/go.mod h1:FJkXmWiMaAo7xB+xhvDF59zhfjDWyzmyAxiT4dB688g= -gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= -gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= diff --git a/hack/installers/checksums/helm-v3.14.0-linux-amd64.tar.gz.sha256 b/hack/installers/checksums/helm-v3.14.0-linux-amd64.tar.gz.sha256 new file mode 100644 index 0000000000000..6f9aaf5a270d5 --- /dev/null +++ b/hack/installers/checksums/helm-v3.14.0-linux-amd64.tar.gz.sha256 @@ -0,0 +1 @@ +f43e1c3387de24547506ab05d24e5309c0ce0b228c23bd8aa64e9ec4b8206651 helm-v3.14.0-linux-amd64.tar.gz diff --git a/hack/installers/checksums/helm-v3.14.0-linux-arm64.tar.gz.sha256 b/hack/installers/checksums/helm-v3.14.0-linux-arm64.tar.gz.sha256 new file mode 100644 index 0000000000000..d0e09bd4b41f7 --- /dev/null +++ b/hack/installers/checksums/helm-v3.14.0-linux-arm64.tar.gz.sha256 @@ -0,0 +1 @@ +b29e61674731b15f6ad3d1a3118a99d3cc2ab25a911aad1b8ac8c72d5a9d2952 helm-v3.14.0-linux-arm64.tar.gz diff --git a/hack/installers/checksums/helm-v3.14.0-linux-ppc64le.tar.gz.sha256 b/hack/installers/checksums/helm-v3.14.0-linux-ppc64le.tar.gz.sha256 new file mode 100644 index 0000000000000..d179322b99dd5 --- /dev/null +++ b/hack/installers/checksums/helm-v3.14.0-linux-ppc64le.tar.gz.sha256 @@ -0,0 +1 @@ +f1f9d3561724863edd4c06d89acb2e2fd8ae0f1b72058ceb891fa1c346ce5dbc helm-v3.14.0-linux-ppc64le.tar.gz diff --git a/hack/installers/checksums/helm-v3.14.0-linux-s390x.tar.gz.sha256 b/hack/installers/checksums/helm-v3.14.0-linux-s390x.tar.gz.sha256 new file mode 100644 index 0000000000000..31ff04397b29e --- /dev/null +++ b/hack/installers/checksums/helm-v3.14.0-linux-s390x.tar.gz.sha256 @@ -0,0 +1 @@ +82298ef39936f1bef848959a29f77bff92d1309d8646657e3a7733702e81288c helm-v3.14.0-linux-s390x.tar.gz diff --git a/hack/tool-versions.sh b/hack/tool-versions.sh index ecc1c424febfa..3cd1bc15aa4c4 100644 --- a/hack/tool-versions.sh +++ b/hack/tool-versions.sh @@ -11,7 +11,7 @@ # Use ./hack/installers/checksums/add-helm-checksums.sh and # add-kustomize-checksums.sh to help download checksums. ############################################################################### -helm3_version=3.13.2 +helm3_version=3.14.0 kubectl_version=1.17.8 kubectx_version=0.6.3 kustomize5_version=5.2.1 diff --git a/manifests/base/application-controller-deployment/argocd-application-controller-deployment.yaml b/manifests/base/application-controller-deployment/argocd-application-controller-deployment.yaml index 0fbf979809c97..68dd75de2f47f 100644 --- a/manifests/base/application-controller-deployment/argocd-application-controller-deployment.yaml +++ b/manifests/base/application-controller-deployment/argocd-application-controller-deployment.yaml @@ -20,8 +20,6 @@ spec: - args: - /usr/local/bin/argocd-application-controller env: - - name: ARGOCD_CONTROLLER_REPLICAS - value: "1" - name: ARGOCD_RECONCILIATION_TIMEOUT valueFrom: configMapKeyRef: @@ -34,6 +32,12 @@ spec: name: argocd-cm key: timeout.hard.reconciliation optional: true + - name: ARGOCD_RECONCILIATION_JITTER + valueFrom: + configMapKeyRef: + key: timeout.reconciliation.jitter + name: argocd-cm + optional: true - name: ARGOCD_REPO_ERROR_GRACE_PERIOD_SECONDS valueFrom: configMapKeyRef: diff --git a/manifests/base/application-controller-deployment/argocd-application-controller-statefulset.yaml b/manifests/base/application-controller-deployment/argocd-application-controller-statefulset.yaml new file mode 100644 index 0000000000000..10e4ea2ac7e3e --- /dev/null +++ b/manifests/base/application-controller-deployment/argocd-application-controller-statefulset.yaml @@ -0,0 +1,15 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: argocd-application-controller +spec: + replicas: 0 + template: + spec: + containers: + - name: argocd-application-controller + args: + - /usr/local/bin/argocd-application-controller + env: + - name: ARGOCD_CONTROLLER_REPLICAS + value: "0" \ No newline at end of file diff --git a/manifests/base/application-controller-deployment/kustomization.yaml b/manifests/base/application-controller-deployment/kustomization.yaml index 8f35ec8bd388f..733a378e013e0 100644 --- a/manifests/base/application-controller-deployment/kustomization.yaml +++ b/manifests/base/application-controller-deployment/kustomization.yaml @@ -2,5 +2,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: +- ../application-controller-roles - argocd-application-controller-service.yaml +- argocd-application-controller-statefulset.yaml - argocd-application-controller-deployment.yaml + diff --git a/manifests/base/application-controller/argocd-application-controller-role.yaml b/manifests/base/application-controller-roles/argocd-application-controller-role.yaml similarity index 100% rename from manifests/base/application-controller/argocd-application-controller-role.yaml rename to manifests/base/application-controller-roles/argocd-application-controller-role.yaml diff --git a/manifests/base/application-controller/argocd-application-controller-rolebinding.yaml b/manifests/base/application-controller-roles/argocd-application-controller-rolebinding.yaml similarity index 100% rename from manifests/base/application-controller/argocd-application-controller-rolebinding.yaml rename to manifests/base/application-controller-roles/argocd-application-controller-rolebinding.yaml diff --git a/manifests/base/application-controller/argocd-application-controller-sa.yaml b/manifests/base/application-controller-roles/argocd-application-controller-sa.yaml similarity index 100% rename from manifests/base/application-controller/argocd-application-controller-sa.yaml rename to manifests/base/application-controller-roles/argocd-application-controller-sa.yaml diff --git a/manifests/base/application-controller-roles/kustomization.yaml b/manifests/base/application-controller-roles/kustomization.yaml new file mode 100644 index 0000000000000..f834d2ef3dbc4 --- /dev/null +++ b/manifests/base/application-controller-roles/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- argocd-application-controller-sa.yaml +- argocd-application-controller-role.yaml +- argocd-application-controller-rolebinding.yaml diff --git a/manifests/base/application-controller/argocd-application-controller-statefulset.yaml b/manifests/base/application-controller/argocd-application-controller-statefulset.yaml index 62f98a1449215..d974edffdd618 100644 --- a/manifests/base/application-controller/argocd-application-controller-statefulset.yaml +++ b/manifests/base/application-controller/argocd-application-controller-statefulset.yaml @@ -35,6 +35,12 @@ spec: name: argocd-cm key: timeout.hard.reconciliation optional: true + - name: ARGOCD_RECONCILIATION_JITTER + valueFrom: + configMapKeyRef: + key: timeout.reconciliation.jitter + name: argocd-cm + optional: true - name: ARGOCD_REPO_ERROR_GRACE_PERIOD_SECONDS valueFrom: configMapKeyRef: diff --git a/manifests/base/application-controller/kustomization.yaml b/manifests/base/application-controller/kustomization.yaml index 9a801ad877bd2..616977fb9b08b 100644 --- a/manifests/base/application-controller/kustomization.yaml +++ b/manifests/base/application-controller/kustomization.yaml @@ -2,9 +2,7 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- argocd-application-controller-sa.yaml -- argocd-application-controller-role.yaml -- argocd-application-controller-rolebinding.yaml +- ../application-controller-roles - argocd-application-controller-statefulset.yaml - argocd-metrics.yaml - argocd-application-controller-network-policy.yaml \ No newline at end of file diff --git a/manifests/base/server/argocd-server-deployment.yaml b/manifests/base/server/argocd-server-deployment.yaml index 6df5f9701713f..0ebeb70e08531 100644 --- a/manifests/base/server/argocd-server-deployment.yaml +++ b/manifests/base/server/argocd-server-deployment.yaml @@ -25,136 +25,136 @@ spec: env: - name: ARGOCD_SERVER_INSECURE valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.insecure - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.insecure + optional: true - name: ARGOCD_SERVER_BASEHREF valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.basehref - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.basehref + optional: true - name: ARGOCD_SERVER_ROOTPATH valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.rootpath - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.rootpath + optional: true - name: ARGOCD_SERVER_LOGFORMAT valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.log.format - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.log.format + optional: true - name: ARGOCD_SERVER_LOG_LEVEL valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.log.level - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.log.level + optional: true - name: ARGOCD_SERVER_REPO_SERVER valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: repo.server - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: repo.server + optional: true - name: ARGOCD_SERVER_DEX_SERVER valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.dex.server - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.dex.server + optional: true - name: ARGOCD_SERVER_DISABLE_AUTH valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.disable.auth - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.disable.auth + optional: true - name: ARGOCD_SERVER_ENABLE_GZIP valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.enable.gzip - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.enable.gzip + optional: true - name: ARGOCD_SERVER_REPO_SERVER_TIMEOUT_SECONDS valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.repo.server.timeout.seconds - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.repo.server.timeout.seconds + optional: true - name: ARGOCD_SERVER_X_FRAME_OPTIONS valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.x.frame.options - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.x.frame.options + optional: true - name: ARGOCD_SERVER_CONTENT_SECURITY_POLICY valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.content.security.policy - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.content.security.policy + optional: true - name: ARGOCD_SERVER_REPO_SERVER_PLAINTEXT valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.repo.server.plaintext - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.repo.server.plaintext + optional: true - name: ARGOCD_SERVER_REPO_SERVER_STRICT_TLS valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.repo.server.strict.tls - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.repo.server.strict.tls + optional: true - name: ARGOCD_SERVER_DEX_SERVER_PLAINTEXT valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.dex.server.plaintext - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.dex.server.plaintext + optional: true - name: ARGOCD_SERVER_DEX_SERVER_STRICT_TLS valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.dex.server.strict.tls - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.dex.server.strict.tls + optional: true - name: ARGOCD_TLS_MIN_VERSION valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.tls.minversion - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.tls.minversion + optional: true - name: ARGOCD_TLS_MAX_VERSION valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.tls.maxversion - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.tls.maxversion + optional: true - name: ARGOCD_TLS_CIPHERS valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.tls.ciphers - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.tls.ciphers + optional: true - name: ARGOCD_SERVER_CONNECTION_STATUS_CACHE_EXPIRATION valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.connection.status.cache.expiration - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.connection.status.cache.expiration + optional: true - name: ARGOCD_SERVER_OIDC_CACHE_EXPIRATION valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.oidc.cache.expiration - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.oidc.cache.expiration + optional: true - name: ARGOCD_SERVER_LOGIN_ATTEMPTS_EXPIRATION valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.login.attempts.expiration - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.login.attempts.expiration + optional: true - name: ARGOCD_SERVER_STATIC_ASSETS valueFrom: configMapKeyRef: @@ -163,16 +163,16 @@ spec: optional: true - name: ARGOCD_APP_STATE_CACHE_EXPIRATION valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.app.state.cache.expiration - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.app.state.cache.expiration + optional: true - name: REDIS_SERVER valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: redis.server - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: redis.server + optional: true - name: REDIS_COMPRESSION valueFrom: configMapKeyRef: @@ -181,76 +181,82 @@ spec: optional: true - name: REDISDB valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: redis.db - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: redis.db + optional: true - name: ARGOCD_DEFAULT_CACHE_EXPIRATION valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.default.cache.expiration - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.default.cache.expiration + optional: true - name: ARGOCD_MAX_COOKIE_NUMBER valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.http.cookie.maxnumber - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.http.cookie.maxnumber + optional: true - name: ARGOCD_SERVER_LISTEN_ADDRESS valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.listen.address - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.listen.address + optional: true - name: ARGOCD_SERVER_METRICS_LISTEN_ADDRESS valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.metrics.listen.address - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.metrics.listen.address + optional: true - name: ARGOCD_SERVER_OTLP_ADDRESS valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: otlp.address - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: otlp.address + optional: true - name: ARGOCD_SERVER_OTLP_INSECURE valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: otlp.insecure - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: otlp.insecure + optional: true - name: ARGOCD_SERVER_OTLP_HEADERS valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: otlp.headers - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: otlp.headers + optional: true - name: ARGOCD_APPLICATION_NAMESPACES valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: application.namespaces - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: application.namespaces + optional: true - name: ARGOCD_SERVER_ENABLE_PROXY_EXTENSION valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.enable.proxy.extension - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.enable.proxy.extension + optional: true - name: ARGOCD_K8SCLIENT_RETRY_MAX valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.k8sclient.retry.max - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.k8sclient.retry.max + optional: true - name: ARGOCD_K8SCLIENT_RETRY_BASE_BACKOFF valueFrom: - configMapKeyRef: - name: argocd-cmd-params-cm - key: server.k8sclient.retry.base.backoff - optional: true + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.k8sclient.retry.base.backoff + optional: true + - name: ARGOCD_API_CONTENT_TYPES + valueFrom: + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.api.content.types + optional: true volumeMounts: - name: ssh-known-hosts mountPath: /app/config/ssh diff --git a/manifests/cluster-rbac/kustomization.yaml b/manifests/cluster-rbac/kustomization.yaml index 7f791905b661b..55e6e2d72df9e 100644 --- a/manifests/cluster-rbac/kustomization.yaml +++ b/manifests/cluster-rbac/kustomization.yaml @@ -3,4 +3,5 @@ kind: Kustomization resources: - ./application-controller +- ./applicationset-controller - ./server diff --git a/manifests/core-install.yaml b/manifests/core-install.yaml index 08d7d972e6362..254cd6e22044f 100644 --- a/manifests/core-install.yaml +++ b/manifests/core-install.yaml @@ -21514,6 +21514,12 @@ spec: key: timeout.hard.reconciliation name: argocd-cm optional: true + - name: ARGOCD_RECONCILIATION_JITTER + valueFrom: + configMapKeyRef: + key: timeout.reconciliation.jitter + name: argocd-cm + optional: true - name: ARGOCD_REPO_ERROR_GRACE_PERIOD_SECONDS valueFrom: configMapKeyRef: diff --git a/manifests/ha/base/controller-deployment/kustomization.yaml b/manifests/ha/base/controller-deployment/kustomization.yaml index d6d20d99b4516..e98bd250d699e 100644 --- a/manifests/ha/base/controller-deployment/kustomization.yaml +++ b/manifests/ha/base/controller-deployment/kustomization.yaml @@ -1,20 +1,17 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization - patches: +- path: argocd-application-controller-statefulset.yaml - path: argocd-repo-server-deployment.yaml - path: argocd-server-deployment.yaml -- path: argocd-application-controller-statefulset.yaml - path: argocd-cmd-params-cm.yaml - images: - name: quay.io/argoproj/argocd newName: quay.io/argoproj/argocd newTag: latest resources: -- ../../../base/application-controller - ../../../base/application-controller-deployment - ../../../base/applicationset-controller - ../../../base/dex diff --git a/manifests/ha/install.yaml b/manifests/ha/install.yaml index a7086ae8a6c06..a092e4d205efd 100644 --- a/manifests/ha/install.yaml +++ b/manifests/ha/install.yaml @@ -20868,6 +20868,95 @@ rules: --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: applicationset-controller + app.kubernetes.io/name: argocd-applicationset-controller + app.kubernetes.io/part-of: argocd + name: argocd-applicationset-controller +rules: +- apiGroups: + - argoproj.io + resources: + - applications + - applicationsets + - applicationsets/finalizers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - argoproj.io + resources: + - applicationsets/status + verbs: + - get + - patch + - update +- apiGroups: + - argoproj.io + resources: + - appprojects + verbs: + - get +- apiGroups: + - "" + resources: + - events + verbs: + - create + - get + - list + - patch + - watch +- apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - update + - delete + - get + - list + - patch + - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch +- apiGroups: + - apps + - extensions + resources: + - deployments + verbs: + - get + - list + - watch +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole metadata: labels: app.kubernetes.io/component: server @@ -21049,6 +21138,23 @@ subjects: --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: applicationset-controller + app.kubernetes.io/name: argocd-applicationset-controller + app.kubernetes.io/part-of: argocd + name: argocd-applicationset-controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argocd-applicationset-controller +subjects: +- kind: ServiceAccount + name: argocd-applicationset-controller + namespace: argocd +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding metadata: labels: app.kubernetes.io/component: server @@ -23221,6 +23327,12 @@ spec: key: server.k8sclient.retry.base.backoff name: argocd-cmd-params-cm optional: true + - name: ARGOCD_API_CONTENT_TYPES + valueFrom: + configMapKeyRef: + key: server.api.content.types + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:latest imagePullPolicy: Always livenessProbe: @@ -23347,6 +23459,12 @@ spec: key: timeout.hard.reconciliation name: argocd-cm optional: true + - name: ARGOCD_RECONCILIATION_JITTER + valueFrom: + configMapKeyRef: + key: timeout.reconciliation.jitter + name: argocd-cm + optional: true - name: ARGOCD_REPO_ERROR_GRACE_PERIOD_SECONDS valueFrom: configMapKeyRef: diff --git a/manifests/ha/namespace-install.yaml b/manifests/ha/namespace-install.yaml index 01a8da2ffd7b9..2c1def5603cc8 100644 --- a/manifests/ha/namespace-install.yaml +++ b/manifests/ha/namespace-install.yaml @@ -2593,6 +2593,12 @@ spec: key: server.k8sclient.retry.base.backoff name: argocd-cmd-params-cm optional: true + - name: ARGOCD_API_CONTENT_TYPES + valueFrom: + configMapKeyRef: + key: server.api.content.types + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:latest imagePullPolicy: Always livenessProbe: @@ -2719,6 +2725,12 @@ spec: key: timeout.hard.reconciliation name: argocd-cm optional: true + - name: ARGOCD_RECONCILIATION_JITTER + valueFrom: + configMapKeyRef: + key: timeout.reconciliation.jitter + name: argocd-cm + optional: true - name: ARGOCD_REPO_ERROR_GRACE_PERIOD_SECONDS valueFrom: configMapKeyRef: diff --git a/manifests/install.yaml b/manifests/install.yaml index 8d30e076d8bf7..40331559f3959 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -20827,6 +20827,95 @@ rules: --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: applicationset-controller + app.kubernetes.io/name: argocd-applicationset-controller + app.kubernetes.io/part-of: argocd + name: argocd-applicationset-controller +rules: +- apiGroups: + - argoproj.io + resources: + - applications + - applicationsets + - applicationsets/finalizers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - argoproj.io + resources: + - applicationsets/status + verbs: + - get + - patch + - update +- apiGroups: + - argoproj.io + resources: + - appprojects + verbs: + - get +- apiGroups: + - "" + resources: + - events + verbs: + - create + - get + - list + - patch + - watch +- apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - update + - delete + - get + - list + - patch + - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch +- apiGroups: + - apps + - extensions + resources: + - deployments + verbs: + - get + - list + - watch +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole metadata: labels: app.kubernetes.io/component: server @@ -20976,6 +21065,23 @@ subjects: --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: applicationset-controller + app.kubernetes.io/name: argocd-applicationset-controller + app.kubernetes.io/part-of: argocd + name: argocd-applicationset-controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argocd-applicationset-controller +subjects: +- kind: ServiceAccount + name: argocd-applicationset-controller + namespace: argocd +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding metadata: labels: app.kubernetes.io/component: server @@ -22265,6 +22371,12 @@ spec: key: server.k8sclient.retry.base.backoff name: argocd-cmd-params-cm optional: true + - name: ARGOCD_API_CONTENT_TYPES + valueFrom: + configMapKeyRef: + key: server.api.content.types + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:latest imagePullPolicy: Always livenessProbe: @@ -22391,6 +22503,12 @@ spec: key: timeout.hard.reconciliation name: argocd-cm optional: true + - name: ARGOCD_RECONCILIATION_JITTER + valueFrom: + configMapKeyRef: + key: timeout.reconciliation.jitter + name: argocd-cm + optional: true - name: ARGOCD_REPO_ERROR_GRACE_PERIOD_SECONDS valueFrom: configMapKeyRef: diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index 76301680f195a..d9cc590df7861 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -1637,6 +1637,12 @@ spec: key: server.k8sclient.retry.base.backoff name: argocd-cmd-params-cm optional: true + - name: ARGOCD_API_CONTENT_TYPES + valueFrom: + configMapKeyRef: + key: server.api.content.types + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:latest imagePullPolicy: Always livenessProbe: @@ -1763,6 +1769,12 @@ spec: key: timeout.hard.reconciliation name: argocd-cm optional: true + - name: ARGOCD_RECONCILIATION_JITTER + valueFrom: + configMapKeyRef: + key: timeout.reconciliation.jitter + name: argocd-cm + optional: true - name: ARGOCD_REPO_ERROR_GRACE_PERIOD_SECONDS valueFrom: configMapKeyRef: diff --git a/pkg/apis/application/v1alpha1/generated.pb.go b/pkg/apis/application/v1alpha1/generated.pb.go index cccbc3f7f15a4..cade795dcebd7 100644 --- a/pkg/apis/application/v1alpha1/generated.pb.go +++ b/pkg/apis/application/v1alpha1/generated.pb.go @@ -4448,694 +4448,695 @@ func init() { } var fileDescriptor_030104ce3b95bcac = []byte{ - // 10990 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x6d, 0x70, 0x1c, 0xc9, - 0x75, 0x98, 0x66, 0x17, 0x0b, 0xec, 0x3e, 0x7c, 0x90, 0x6c, 0x92, 0x77, 0x20, 0x75, 0x77, 0xa0, - 0xe7, 0xe2, 0xd3, 0x39, 0xba, 0x03, 0x7c, 0xf4, 0x9d, 0x7c, 0xf1, 0xd9, 0x92, 0xb1, 0x00, 0x09, - 0x82, 0x04, 0x08, 0x5c, 0x03, 0x24, 0xa5, 0x93, 0x4f, 0xa7, 0xc1, 0x6e, 0x63, 0x31, 0xc4, 0xec, - 0xcc, 0xdc, 0xcc, 0x2c, 0x08, 0x9c, 0x25, 0x59, 0xb2, 0x64, 0x5b, 0x89, 0x3e, 0x4e, 0x91, 0x92, - 0xf2, 0x39, 0x89, 0x14, 0xd9, 0x72, 0x52, 0x76, 0x25, 0xaa, 0x38, 0xc9, 0x8f, 0x38, 0x71, 0x52, - 0x2e, 0xdb, 0xa9, 0x94, 0x52, 0x4a, 0xca, 0x2e, 0x97, 0xcb, 0x72, 0x12, 0x1b, 0x91, 0x98, 0x4a, - 0x25, 0x95, 0xaa, 0xb8, 0xca, 0x89, 0x7f, 0x24, 0x4c, 0x7e, 0xa4, 0xfa, 0xbb, 0x67, 0x76, 0x16, - 0x58, 0x00, 0x03, 0x92, 0x52, 0xee, 0xdf, 0x6e, 0xbf, 0x37, 0xef, 0xf5, 0xf4, 0x74, 0xbf, 0xf7, - 0xfa, 0xf5, 0x7b, 0xaf, 0x61, 0xa1, 0xe5, 0x26, 0x1b, 0x9d, 0xb5, 0xc9, 0x46, 0xd0, 0x9e, 0x72, - 0xa2, 0x56, 0x10, 0x46, 0xc1, 0x6d, 0xf6, 0xe3, 0xd9, 0x46, 0x73, 0x6a, 0xeb, 0xe2, 0x54, 0xb8, - 0xd9, 0x9a, 0x72, 0x42, 0x37, 0x9e, 0x72, 0xc2, 0xd0, 0x73, 0x1b, 0x4e, 0xe2, 0x06, 0xfe, 0xd4, - 0xd6, 0x73, 0x8e, 0x17, 0x6e, 0x38, 0xcf, 0x4d, 0xb5, 0x88, 0x4f, 0x22, 0x27, 0x21, 0xcd, 0xc9, - 0x30, 0x0a, 0x92, 0x00, 0xfd, 0xa8, 0xa6, 0x36, 0x29, 0xa9, 0xb1, 0x1f, 0xaf, 0x35, 0x9a, 0x93, - 0x5b, 0x17, 0x27, 0xc3, 0xcd, 0xd6, 0x24, 0xa5, 0x36, 0x69, 0x50, 0x9b, 0x94, 0xd4, 0xce, 0x3f, - 0x6b, 0xf4, 0xa5, 0x15, 0xb4, 0x82, 0x29, 0x46, 0x74, 0xad, 0xb3, 0xce, 0xfe, 0xb1, 0x3f, 0xec, - 0x17, 0x67, 0x76, 0xde, 0xde, 0x7c, 0x31, 0x9e, 0x74, 0x03, 0xda, 0xbd, 0xa9, 0x46, 0x10, 0x91, - 0xa9, 0xad, 0xae, 0x0e, 0x9d, 0xbf, 0xa2, 0x71, 0xc8, 0x76, 0x42, 0xfc, 0xd8, 0x0d, 0xfc, 0xf8, - 0x59, 0xda, 0x05, 0x12, 0x6d, 0x91, 0xc8, 0x7c, 0x3d, 0x03, 0x21, 0x8f, 0xd2, 0xf3, 0x9a, 0x52, - 0xdb, 0x69, 0x6c, 0xb8, 0x3e, 0x89, 0x76, 0xf4, 0xe3, 0x6d, 0x92, 0x38, 0x79, 0x4f, 0x4d, 0xf5, - 0x7a, 0x2a, 0xea, 0xf8, 0x89, 0xdb, 0x26, 0x5d, 0x0f, 0xbc, 0x67, 0xbf, 0x07, 0xe2, 0xc6, 0x06, - 0x69, 0x3b, 0x5d, 0xcf, 0xfd, 0x50, 0xaf, 0xe7, 0x3a, 0x89, 0xeb, 0x4d, 0xb9, 0x7e, 0x12, 0x27, - 0x51, 0xf6, 0x21, 0xfb, 0x75, 0x18, 0x9d, 0xbe, 0xb5, 0x32, 0xdd, 0x49, 0x36, 0x66, 0x02, 0x7f, - 0xdd, 0x6d, 0xa1, 0x17, 0x60, 0xb8, 0xe1, 0x75, 0xe2, 0x84, 0x44, 0xd7, 0x9d, 0x36, 0x19, 0xb7, - 0x2e, 0x58, 0x4f, 0xd7, 0xea, 0xa7, 0xbf, 0xb1, 0x3b, 0xf1, 0x8e, 0xbb, 0xbb, 0x13, 0xc3, 0x33, - 0x1a, 0x84, 0x4d, 0x3c, 0xf4, 0x03, 0x30, 0x14, 0x05, 0x1e, 0x99, 0xc6, 0xd7, 0xc7, 0x4b, 0xec, - 0x91, 0x13, 0xe2, 0x91, 0x21, 0xcc, 0x9b, 0xb1, 0x84, 0xdb, 0x7f, 0x58, 0x02, 0x98, 0x0e, 0xc3, - 0xe5, 0x28, 0xb8, 0x4d, 0x1a, 0x09, 0xfa, 0x30, 0x54, 0xe9, 0xd0, 0x35, 0x9d, 0xc4, 0x61, 0xdc, - 0x86, 0x2f, 0xfe, 0xe0, 0x24, 0x7f, 0x93, 0x49, 0xf3, 0x4d, 0xf4, 0xc4, 0xa1, 0xd8, 0x93, 0x5b, - 0xcf, 0x4d, 0x2e, 0xad, 0xd1, 0xe7, 0x17, 0x49, 0xe2, 0xd4, 0x91, 0x60, 0x06, 0xba, 0x0d, 0x2b, - 0xaa, 0xc8, 0x87, 0x81, 0x38, 0x24, 0x0d, 0xd6, 0xb1, 0xe1, 0x8b, 0x0b, 0x93, 0x47, 0x99, 0xa1, - 0x93, 0xba, 0xe7, 0x2b, 0x21, 0x69, 0xd4, 0x47, 0x04, 0xe7, 0x01, 0xfa, 0x0f, 0x33, 0x3e, 0x68, - 0x0b, 0x06, 0xe3, 0xc4, 0x49, 0x3a, 0xf1, 0x78, 0x99, 0x71, 0xbc, 0x5e, 0x18, 0x47, 0x46, 0xb5, - 0x3e, 0x26, 0x78, 0x0e, 0xf2, 0xff, 0x58, 0x70, 0xb3, 0xff, 0xc4, 0x82, 0x31, 0x8d, 0xbc, 0xe0, - 0xc6, 0x09, 0xfa, 0x89, 0xae, 0xc1, 0x9d, 0xec, 0x6f, 0x70, 0xe9, 0xd3, 0x6c, 0x68, 0x4f, 0x0a, - 0x66, 0x55, 0xd9, 0x62, 0x0c, 0x6c, 0x1b, 0x2a, 0x6e, 0x42, 0xda, 0xf1, 0x78, 0xe9, 0x42, 0xf9, - 0xe9, 0xe1, 0x8b, 0x57, 0x8a, 0x7a, 0xcf, 0xfa, 0xa8, 0x60, 0x5a, 0x99, 0xa7, 0xe4, 0x31, 0xe7, - 0x62, 0xff, 0xea, 0x88, 0xf9, 0x7e, 0x74, 0xc0, 0xd1, 0x73, 0x30, 0x1c, 0x07, 0x9d, 0xa8, 0x41, - 0x30, 0x09, 0x83, 0x78, 0xdc, 0xba, 0x50, 0xa6, 0x53, 0x8f, 0xce, 0xd4, 0x15, 0xdd, 0x8c, 0x4d, - 0x1c, 0xf4, 0x79, 0x0b, 0x46, 0x9a, 0x24, 0x4e, 0x5c, 0x9f, 0xf1, 0x97, 0x9d, 0x5f, 0x3d, 0x72, - 0xe7, 0x65, 0xe3, 0xac, 0x26, 0x5e, 0x3f, 0x23, 0x5e, 0x64, 0xc4, 0x68, 0x8c, 0x71, 0x8a, 0x3f, - 0x5d, 0x71, 0x4d, 0x12, 0x37, 0x22, 0x37, 0xa4, 0xff, 0xd9, 0x9c, 0x31, 0x56, 0xdc, 0xac, 0x06, - 0x61, 0x13, 0x0f, 0xf9, 0x50, 0xa1, 0x2b, 0x2a, 0x1e, 0x1f, 0x60, 0xfd, 0x9f, 0x3f, 0x5a, 0xff, - 0xc5, 0xa0, 0xd2, 0xc5, 0xaa, 0x47, 0x9f, 0xfe, 0x8b, 0x31, 0x67, 0x83, 0x3e, 0x67, 0xc1, 0xb8, - 0x58, 0xf1, 0x98, 0xf0, 0x01, 0xbd, 0xb5, 0xe1, 0x26, 0xc4, 0x73, 0xe3, 0x64, 0xbc, 0xc2, 0xfa, - 0x30, 0xd5, 0xdf, 0xdc, 0x9a, 0x8b, 0x82, 0x4e, 0x78, 0xcd, 0xf5, 0x9b, 0xf5, 0x0b, 0x82, 0xd3, - 0xf8, 0x4c, 0x0f, 0xc2, 0xb8, 0x27, 0x4b, 0xf4, 0x25, 0x0b, 0xce, 0xfb, 0x4e, 0x9b, 0xc4, 0xa1, - 0x43, 0x3f, 0x2d, 0x07, 0xd7, 0x3d, 0xa7, 0xb1, 0xc9, 0x7a, 0x34, 0x78, 0xb8, 0x1e, 0xd9, 0xa2, - 0x47, 0xe7, 0xaf, 0xf7, 0x24, 0x8d, 0xf7, 0x60, 0x8b, 0xbe, 0x66, 0xc1, 0xa9, 0x20, 0x0a, 0x37, - 0x1c, 0x9f, 0x34, 0x25, 0x34, 0x1e, 0x1f, 0x62, 0x4b, 0xef, 0x43, 0x47, 0xfb, 0x44, 0x4b, 0x59, - 0xb2, 0x8b, 0x81, 0xef, 0x26, 0x41, 0xb4, 0x42, 0x92, 0xc4, 0xf5, 0x5b, 0x71, 0xfd, 0xec, 0xdd, - 0xdd, 0x89, 0x53, 0x5d, 0x58, 0xb8, 0xbb, 0x3f, 0xe8, 0x27, 0x61, 0x38, 0xde, 0xf1, 0x1b, 0xb7, - 0x5c, 0xbf, 0x19, 0xdc, 0x89, 0xc7, 0xab, 0x45, 0x2c, 0xdf, 0x15, 0x45, 0x50, 0x2c, 0x40, 0xcd, - 0x00, 0x9b, 0xdc, 0xf2, 0x3f, 0x9c, 0x9e, 0x4a, 0xb5, 0xa2, 0x3f, 0x9c, 0x9e, 0x4c, 0x7b, 0xb0, - 0x45, 0x3f, 0x67, 0xc1, 0x68, 0xec, 0xb6, 0x7c, 0x27, 0xe9, 0x44, 0xe4, 0x1a, 0xd9, 0x89, 0xc7, - 0x81, 0x75, 0xe4, 0xea, 0x11, 0x47, 0xc5, 0x20, 0x59, 0x3f, 0x2b, 0xfa, 0x38, 0x6a, 0xb6, 0xc6, - 0x38, 0xcd, 0x37, 0x6f, 0xa1, 0xe9, 0x69, 0x3d, 0x5c, 0xec, 0x42, 0xd3, 0x93, 0xba, 0x27, 0x4b, - 0xf4, 0xe3, 0x70, 0x92, 0x37, 0xa9, 0x91, 0x8d, 0xc7, 0x47, 0x98, 0xa0, 0x3d, 0x73, 0x77, 0x77, - 0xe2, 0xe4, 0x4a, 0x06, 0x86, 0xbb, 0xb0, 0xd1, 0xeb, 0x30, 0x11, 0x92, 0xa8, 0xed, 0x26, 0x4b, - 0xbe, 0xb7, 0x23, 0xc5, 0x77, 0x23, 0x08, 0x49, 0x53, 0x74, 0x27, 0x1e, 0x1f, 0xbd, 0x60, 0x3d, - 0x5d, 0xad, 0xbf, 0x4b, 0x74, 0x73, 0x62, 0x79, 0x6f, 0x74, 0xbc, 0x1f, 0x3d, 0xfb, 0x5f, 0x97, - 0xe0, 0x64, 0x56, 0x71, 0xa2, 0xbf, 0x6b, 0xc1, 0x89, 0xdb, 0x77, 0x92, 0xd5, 0x60, 0x93, 0xf8, - 0x71, 0x7d, 0x87, 0x8a, 0x37, 0xa6, 0x32, 0x86, 0x2f, 0x36, 0x8a, 0x55, 0xd1, 0x93, 0x57, 0xd3, - 0x5c, 0x2e, 0xf9, 0x49, 0xb4, 0x53, 0x7f, 0x54, 0xbc, 0xdd, 0x89, 0xab, 0xb7, 0x56, 0x4d, 0x28, - 0xce, 0x76, 0xea, 0xfc, 0x67, 0x2c, 0x38, 0x93, 0x47, 0x02, 0x9d, 0x84, 0xf2, 0x26, 0xd9, 0xe1, - 0x56, 0x19, 0xa6, 0x3f, 0xd1, 0xab, 0x50, 0xd9, 0x72, 0xbc, 0x0e, 0x11, 0xd6, 0xcd, 0xdc, 0xd1, - 0x5e, 0x44, 0xf5, 0x0c, 0x73, 0xaa, 0x3f, 0x52, 0x7a, 0xd1, 0xb2, 0x7f, 0xb7, 0x0c, 0xc3, 0x86, - 0x7e, 0xbb, 0x0f, 0x16, 0x5b, 0x90, 0xb2, 0xd8, 0x16, 0x0b, 0x53, 0xcd, 0x3d, 0x4d, 0xb6, 0x3b, - 0x19, 0x93, 0x6d, 0xa9, 0x38, 0x96, 0x7b, 0xda, 0x6c, 0x28, 0x81, 0x5a, 0x10, 0x52, 0x8b, 0x9c, - 0xaa, 0xfe, 0x81, 0x22, 0x3e, 0xe1, 0x92, 0x24, 0x57, 0x1f, 0xbd, 0xbb, 0x3b, 0x51, 0x53, 0x7f, - 0xb1, 0x66, 0x64, 0x7f, 0xcb, 0x82, 0x33, 0x46, 0x1f, 0x67, 0x02, 0xbf, 0xe9, 0xb2, 0x4f, 0x7b, - 0x01, 0x06, 0x92, 0x9d, 0x50, 0x9a, 0xfd, 0x6a, 0xa4, 0x56, 0x77, 0x42, 0x82, 0x19, 0x84, 0x1a, - 0xfa, 0x6d, 0x12, 0xc7, 0x4e, 0x8b, 0x64, 0x0d, 0xfd, 0x45, 0xde, 0x8c, 0x25, 0x1c, 0x45, 0x80, - 0x3c, 0x27, 0x4e, 0x56, 0x23, 0xc7, 0x8f, 0x19, 0xf9, 0x55, 0xb7, 0x4d, 0xc4, 0x00, 0xff, 0xc5, - 0xfe, 0x66, 0x0c, 0x7d, 0xa2, 0xfe, 0xc8, 0xdd, 0xdd, 0x09, 0xb4, 0xd0, 0x45, 0x09, 0xe7, 0x50, - 0xb7, 0xbf, 0x64, 0xc1, 0x23, 0xf9, 0xb6, 0x18, 0x7a, 0x0a, 0x06, 0xf9, 0x96, 0x4f, 0xbc, 0x9d, - 0xfe, 0x24, 0xac, 0x15, 0x0b, 0x28, 0x9a, 0x82, 0x9a, 0xd2, 0x13, 0xe2, 0x1d, 0x4f, 0x09, 0xd4, - 0x9a, 0x56, 0x2e, 0x1a, 0x87, 0x0e, 0x1a, 0xfd, 0x23, 0x2c, 0x37, 0x35, 0x68, 0x6c, 0x93, 0xc4, - 0x20, 0xf6, 0x7f, 0xb4, 0xe0, 0x84, 0xd1, 0xab, 0xfb, 0x60, 0x9a, 0xfb, 0x69, 0xd3, 0x7c, 0xbe, - 0xb0, 0xf9, 0xdc, 0xc3, 0x36, 0xff, 0x9c, 0x05, 0xe7, 0x0d, 0xac, 0x45, 0x27, 0x69, 0x6c, 0x5c, - 0xda, 0x0e, 0x23, 0x12, 0xd3, 0xed, 0x34, 0x7a, 0xdc, 0x90, 0x5b, 0xf5, 0x61, 0x41, 0xa1, 0x7c, - 0x8d, 0xec, 0x70, 0x21, 0xf6, 0x0c, 0x54, 0xf9, 0xe4, 0x0c, 0x22, 0x31, 0xe2, 0xea, 0xdd, 0x96, - 0x44, 0x3b, 0x56, 0x18, 0xc8, 0x86, 0x41, 0x26, 0x9c, 0xe8, 0x62, 0xa5, 0x6a, 0x08, 0xe8, 0x47, - 0xbc, 0xc9, 0x5a, 0xb0, 0x80, 0xd8, 0x71, 0xaa, 0x3b, 0xcb, 0x11, 0x61, 0x1f, 0xb7, 0x79, 0xd9, - 0x25, 0x5e, 0x33, 0xa6, 0xdb, 0x06, 0xc7, 0xf7, 0x83, 0x44, 0xec, 0x00, 0x8c, 0x6d, 0xc3, 0xb4, - 0x6e, 0xc6, 0x26, 0x0e, 0x65, 0xea, 0x39, 0x6b, 0xc4, 0xe3, 0x23, 0x2a, 0x98, 0x2e, 0xb0, 0x16, - 0x2c, 0x20, 0xf6, 0xdd, 0x12, 0xdb, 0xa0, 0xa8, 0xa5, 0x4f, 0xee, 0xc7, 0xee, 0x36, 0x4a, 0xc9, - 0xca, 0xe5, 0xe2, 0x04, 0x17, 0xe9, 0xbd, 0xc3, 0x7d, 0x23, 0x23, 0x2e, 0x71, 0xa1, 0x5c, 0xf7, - 0xde, 0xe5, 0xfe, 0x56, 0x09, 0x26, 0xd2, 0x0f, 0x74, 0x49, 0x5b, 0xba, 0xa5, 0x32, 0x18, 0x65, - 0x9d, 0x18, 0x06, 0x3e, 0x36, 0xf1, 0x7a, 0x08, 0xac, 0xd2, 0x71, 0x0a, 0x2c, 0x53, 0x9e, 0x96, - 0xf7, 0x91, 0xa7, 0x4f, 0xa9, 0x51, 0x1f, 0xc8, 0x08, 0xb0, 0xb4, 0x4e, 0xb9, 0x00, 0x03, 0x71, - 0x42, 0xc2, 0xf1, 0x4a, 0x5a, 0x1e, 0xad, 0x24, 0x24, 0xc4, 0x0c, 0x62, 0xff, 0xb7, 0x12, 0x3c, - 0x9a, 0x1e, 0x43, 0xad, 0x02, 0xde, 0x97, 0x52, 0x01, 0xef, 0x36, 0x55, 0xc0, 0xbd, 0xdd, 0x89, - 0x77, 0xf6, 0x78, 0xec, 0xbb, 0x46, 0x43, 0xa0, 0xb9, 0xcc, 0x28, 0x4e, 0xa5, 0x47, 0xf1, 0xde, - 0xee, 0xc4, 0xe3, 0x3d, 0xde, 0x31, 0x33, 0xcc, 0x4f, 0xc1, 0x60, 0x44, 0x9c, 0x38, 0xf0, 0xc5, - 0x40, 0xab, 0xcf, 0x81, 0x59, 0x2b, 0x16, 0x50, 0xfb, 0xf7, 0x6b, 0xd9, 0xc1, 0x9e, 0xe3, 0x4e, - 0xb8, 0x20, 0x42, 0x2e, 0x0c, 0x30, 0xb3, 0x9e, 0x8b, 0x86, 0x6b, 0x47, 0x5b, 0x46, 0x54, 0x0d, - 0x28, 0xd2, 0xf5, 0x2a, 0xfd, 0x6a, 0xb4, 0x09, 0x33, 0x16, 0x68, 0x1b, 0xaa, 0x0d, 0x69, 0x6d, - 0x97, 0x8a, 0xf0, 0x4b, 0x09, 0x5b, 0x5b, 0x73, 0x1c, 0xa1, 0xf2, 0x5a, 0x99, 0xe8, 0x8a, 0x1b, - 0x22, 0x50, 0x6e, 0xb9, 0x89, 0xf8, 0xac, 0x47, 0xdc, 0x4f, 0xcd, 0xb9, 0xc6, 0x2b, 0x0e, 0x51, - 0x25, 0x32, 0xe7, 0x26, 0x98, 0xd2, 0x47, 0x3f, 0x63, 0xc1, 0x70, 0xdc, 0x68, 0x2f, 0x47, 0xc1, - 0x96, 0xdb, 0x24, 0x91, 0xb0, 0xa6, 0x8e, 0x28, 0x9a, 0x56, 0x66, 0x16, 0x25, 0x41, 0xcd, 0x97, - 0xef, 0x6f, 0x35, 0x04, 0x9b, 0x7c, 0xe9, 0x2e, 0xe3, 0x51, 0xf1, 0xee, 0xb3, 0xa4, 0xe1, 0x52, - 0xfd, 0x27, 0x37, 0x55, 0x6c, 0xa6, 0x1c, 0xd9, 0xba, 0x9c, 0xed, 0x34, 0x36, 0xe9, 0x7a, 0xd3, - 0x1d, 0x7a, 0xe7, 0xdd, 0xdd, 0x89, 0x47, 0x67, 0xf2, 0x79, 0xe2, 0x5e, 0x9d, 0x61, 0x03, 0x16, - 0x76, 0x3c, 0x0f, 0x93, 0xd7, 0x3b, 0x84, 0xb9, 0x4c, 0x0a, 0x18, 0xb0, 0x65, 0x4d, 0x30, 0x33, - 0x60, 0x06, 0x04, 0x9b, 0x7c, 0xd1, 0xeb, 0x30, 0xd8, 0x76, 0x92, 0xc8, 0xdd, 0x16, 0x7e, 0x92, - 0x23, 0xda, 0xfb, 0x8b, 0x8c, 0x96, 0x66, 0xce, 0x34, 0x35, 0x6f, 0xc4, 0x82, 0x11, 0x6a, 0x43, - 0xa5, 0x4d, 0xa2, 0x16, 0x19, 0xaf, 0x16, 0xe1, 0x13, 0x5e, 0xa4, 0xa4, 0x34, 0xc3, 0x1a, 0xb5, - 0x8e, 0x58, 0x1b, 0xe6, 0x5c, 0xd0, 0xab, 0x50, 0x8d, 0x89, 0x47, 0x1a, 0xd4, 0xbe, 0xa9, 0x31, - 0x8e, 0x3f, 0xd4, 0xa7, 0xad, 0x47, 0x0d, 0x8b, 0x15, 0xf1, 0x28, 0x5f, 0x60, 0xf2, 0x1f, 0x56, - 0x24, 0xe9, 0x00, 0x86, 0x5e, 0xa7, 0xe5, 0xfa, 0xe3, 0x50, 0xc4, 0x00, 0x2e, 0x33, 0x5a, 0x99, - 0x01, 0xe4, 0x8d, 0x58, 0x30, 0xb2, 0xff, 0xb3, 0x05, 0x28, 0x2d, 0xd4, 0xee, 0x83, 0x51, 0xfb, - 0x7a, 0xda, 0xa8, 0x5d, 0x28, 0xd2, 0xea, 0xe8, 0x61, 0xd7, 0xfe, 0x46, 0x0d, 0x32, 0xea, 0xe0, - 0x3a, 0x89, 0x13, 0xd2, 0x7c, 0x5b, 0x84, 0xbf, 0x2d, 0xc2, 0xdf, 0x16, 0xe1, 0x4a, 0x84, 0xaf, - 0x65, 0x44, 0xf8, 0x7b, 0x8d, 0x55, 0xaf, 0x0f, 0x55, 0x5f, 0x53, 0xa7, 0xae, 0x66, 0x0f, 0x0c, - 0x04, 0x2a, 0x09, 0xae, 0xae, 0x2c, 0x5d, 0xcf, 0x95, 0xd9, 0xaf, 0xa5, 0x65, 0xf6, 0x51, 0x59, - 0xfc, 0xff, 0x20, 0xa5, 0xff, 0x95, 0x05, 0xef, 0x4a, 0x4b, 0x2f, 0x39, 0x73, 0xe6, 0x5b, 0x7e, - 0x10, 0x91, 0x59, 0x77, 0x7d, 0x9d, 0x44, 0xc4, 0x6f, 0x90, 0x58, 0x79, 0x31, 0xac, 0x5e, 0x5e, - 0x0c, 0xf4, 0x3c, 0x8c, 0xdc, 0x8e, 0x03, 0x7f, 0x39, 0x70, 0x7d, 0x21, 0x82, 0xe8, 0x46, 0xf8, - 0xe4, 0xdd, 0xdd, 0x89, 0x11, 0x3a, 0xa2, 0xb2, 0x1d, 0xa7, 0xb0, 0xd0, 0x0c, 0x9c, 0xba, 0xfd, - 0xfa, 0xb2, 0x93, 0x18, 0xee, 0x00, 0xb9, 0x71, 0x67, 0x07, 0x16, 0x57, 0x5f, 0xce, 0x00, 0x71, - 0x37, 0xbe, 0xfd, 0x37, 0x4b, 0x70, 0x2e, 0xf3, 0x22, 0x81, 0xe7, 0x05, 0x9d, 0x84, 0x6e, 0x6a, - 0xd0, 0x57, 0x2c, 0x38, 0xd9, 0x4e, 0x7b, 0x1c, 0x62, 0xe1, 0xd8, 0x7d, 0x7f, 0x61, 0x3a, 0x22, - 0xe3, 0xd2, 0xa8, 0x8f, 0x8b, 0x11, 0x3a, 0x99, 0x01, 0xc4, 0xb8, 0xab, 0x2f, 0xe8, 0x55, 0xa8, - 0xb5, 0x9d, 0xed, 0x1b, 0x61, 0xd3, 0x49, 0xe4, 0x7e, 0xb2, 0xb7, 0x1b, 0xa0, 0x93, 0xb8, 0xde, - 0x24, 0x3f, 0xae, 0x9f, 0x9c, 0xf7, 0x93, 0xa5, 0x68, 0x25, 0x89, 0x5c, 0xbf, 0xc5, 0xdd, 0x79, - 0x8b, 0x92, 0x0c, 0xd6, 0x14, 0xed, 0x2f, 0x5b, 0x59, 0x25, 0xa5, 0x46, 0x27, 0x72, 0x12, 0xd2, - 0xda, 0x41, 0x1f, 0x81, 0x0a, 0xdd, 0xf8, 0xc9, 0x51, 0xb9, 0x55, 0xa4, 0xe6, 0x34, 0xbe, 0x84, - 0x56, 0xa2, 0xf4, 0x5f, 0x8c, 0x39, 0x53, 0xfb, 0x2b, 0xb5, 0xac, 0xb1, 0xc0, 0x0e, 0x6f, 0x2f, - 0x02, 0xb4, 0x82, 0x55, 0xd2, 0x0e, 0x3d, 0x3a, 0x2c, 0x16, 0x3b, 0x01, 0x50, 0xbe, 0x8e, 0x39, - 0x05, 0xc1, 0x06, 0x16, 0xfa, 0xcb, 0x16, 0x40, 0x4b, 0xce, 0x79, 0x69, 0x08, 0xdc, 0x28, 0xf2, - 0x75, 0xf4, 0x8a, 0xd2, 0x7d, 0x51, 0x0c, 0xb1, 0xc1, 0x1c, 0xfd, 0xb4, 0x05, 0xd5, 0x44, 0x76, - 0x9f, 0xab, 0xc6, 0xd5, 0x22, 0x7b, 0x22, 0x5f, 0x5a, 0xdb, 0x44, 0x6a, 0x48, 0x14, 0x5f, 0xf4, - 0xb3, 0x16, 0x40, 0xbc, 0xe3, 0x37, 0x96, 0x03, 0xcf, 0x6d, 0xec, 0x08, 0x8d, 0x79, 0xb3, 0x50, - 0x7f, 0x8c, 0xa2, 0x5e, 0x1f, 0xa3, 0xa3, 0xa1, 0xff, 0x63, 0x83, 0x33, 0xfa, 0x18, 0x54, 0x63, - 0x31, 0xdd, 0x84, 0x8e, 0x5c, 0x2d, 0xd6, 0x2b, 0xc4, 0x69, 0x0b, 0xf1, 0x2a, 0xfe, 0x61, 0xc5, - 0x13, 0xfd, 0xbc, 0x05, 0x27, 0xc2, 0xb4, 0x9f, 0x4f, 0xa8, 0xc3, 0xe2, 0x64, 0x40, 0xc6, 0x8f, - 0x58, 0x3f, 0x7d, 0x77, 0x77, 0xe2, 0x44, 0xa6, 0x11, 0x67, 0x7b, 0x41, 0x25, 0xa0, 0x9e, 0xc1, - 0x4b, 0x21, 0xf7, 0x39, 0x0e, 0x69, 0x09, 0x38, 0x97, 0x05, 0xe2, 0x6e, 0x7c, 0xb4, 0x0c, 0x67, - 0x68, 0xef, 0x76, 0xb8, 0xf9, 0x29, 0xd5, 0x4b, 0xcc, 0x94, 0x61, 0xb5, 0xfe, 0x98, 0x98, 0x21, - 0xcc, 0xab, 0x9f, 0xc5, 0xc1, 0xb9, 0x4f, 0xa2, 0xdf, 0xb5, 0xe0, 0x31, 0x97, 0xa9, 0x01, 0xd3, - 0x61, 0xae, 0x35, 0x82, 0x38, 0x89, 0x25, 0x85, 0xca, 0x8a, 0x5e, 0xea, 0xa7, 0xfe, 0x17, 0xc4, - 0x1b, 0x3c, 0x36, 0xbf, 0x47, 0x97, 0xf0, 0x9e, 0x1d, 0x46, 0x3f, 0x0c, 0xa3, 0x72, 0x5d, 0x2c, - 0x53, 0x11, 0xcc, 0x14, 0x6d, 0xad, 0x7e, 0xea, 0xee, 0xee, 0xc4, 0xe8, 0xaa, 0x09, 0xc0, 0x69, - 0x3c, 0xfb, 0x9b, 0xa5, 0xd4, 0x79, 0x88, 0x72, 0x42, 0x32, 0x71, 0xd3, 0x90, 0xfe, 0x1f, 0x29, - 0x3d, 0x0b, 0x15, 0x37, 0xca, 0xbb, 0xa4, 0xc5, 0x8d, 0x6a, 0x8a, 0xb1, 0xc1, 0x9c, 0x1a, 0xa5, - 0xa7, 0x9c, 0xac, 0xab, 0x53, 0x48, 0xc0, 0x57, 0x8b, 0xec, 0x52, 0xf7, 0xe9, 0xd5, 0x39, 0xd1, - 0xb5, 0x53, 0x5d, 0x20, 0xdc, 0xdd, 0x25, 0xfb, 0x9b, 0xe9, 0x33, 0x18, 0x63, 0xf1, 0xf6, 0x71, - 0xbe, 0xf4, 0x79, 0x0b, 0x86, 0xa3, 0xc0, 0xf3, 0x5c, 0xbf, 0x45, 0x05, 0x8d, 0xd0, 0x96, 0x1f, - 0x3c, 0x16, 0x85, 0x25, 0x24, 0x0a, 0x33, 0x6d, 0xb1, 0xe6, 0x89, 0xcd, 0x0e, 0xd8, 0x7f, 0x62, - 0xc1, 0x78, 0x2f, 0x81, 0x88, 0x08, 0xbc, 0x53, 0xae, 0x76, 0x15, 0x5d, 0xb1, 0xe4, 0xcf, 0x12, - 0x8f, 0x28, 0xc7, 0x73, 0xb5, 0xfe, 0xa4, 0x78, 0xcd, 0x77, 0x2e, 0xf7, 0x46, 0xc5, 0x7b, 0xd1, - 0x41, 0xaf, 0xc0, 0x49, 0xe3, 0xbd, 0x62, 0x35, 0x30, 0xb5, 0xfa, 0x24, 0xb5, 0x40, 0xa6, 0x33, - 0xb0, 0x7b, 0xbb, 0x13, 0x8f, 0x64, 0xdb, 0x84, 0xc4, 0xee, 0xa2, 0x63, 0xff, 0x72, 0x29, 0xfb, - 0xb5, 0x94, 0xb2, 0x7d, 0xcb, 0xea, 0xda, 0xce, 0xbf, 0xff, 0x38, 0x14, 0x1c, 0xdb, 0xf8, 0xab, - 0x00, 0x8e, 0xde, 0x38, 0x0f, 0xf0, 0x84, 0xd8, 0xfe, 0x37, 0x03, 0xb0, 0x47, 0xcf, 0xfa, 0xb0, - 0x9e, 0x0f, 0x7c, 0xac, 0xf8, 0x59, 0x4b, 0x1d, 0x39, 0x95, 0xd9, 0x22, 0x6f, 0x1e, 0xd7, 0xd8, - 0xf3, 0x0d, 0x4c, 0xcc, 0xa3, 0x14, 0x94, 0x1b, 0x3b, 0x7d, 0xb8, 0x85, 0xbe, 0x6a, 0xa5, 0x0f, - 0xcd, 0x78, 0xd8, 0x99, 0x7b, 0x6c, 0x7d, 0x32, 0x4e, 0xe2, 0x78, 0xc7, 0xf4, 0xf9, 0x4d, 0xaf, - 0x33, 0xba, 0x49, 0x80, 0x75, 0xd7, 0x77, 0x3c, 0xf7, 0x0d, 0xba, 0x3d, 0xa9, 0x30, 0x0d, 0xcb, - 0x4c, 0x96, 0xcb, 0xaa, 0x15, 0x1b, 0x18, 0xe7, 0xff, 0x12, 0x0c, 0x1b, 0x6f, 0x9e, 0x13, 0x5c, - 0x71, 0xc6, 0x0c, 0xae, 0xa8, 0x19, 0x31, 0x11, 0xe7, 0xdf, 0x0b, 0x27, 0xb3, 0x1d, 0x3c, 0xc8, - 0xf3, 0xf6, 0xff, 0x1a, 0xca, 0x9e, 0x62, 0xad, 0x92, 0xa8, 0x4d, 0xbb, 0xf6, 0xb6, 0x67, 0xe9, - 0x6d, 0xcf, 0xd2, 0xdb, 0x9e, 0x25, 0xf3, 0x70, 0x40, 0x78, 0x4d, 0x86, 0xee, 0x93, 0xd7, 0x24, - 0xe5, 0x07, 0xaa, 0x16, 0xee, 0x07, 0xb2, 0xef, 0x56, 0x20, 0x65, 0x47, 0xf1, 0xf1, 0xfe, 0x01, - 0x18, 0x8a, 0x48, 0x18, 0xdc, 0xc0, 0x0b, 0x42, 0x87, 0xe8, 0x00, 0x7a, 0xde, 0x8c, 0x25, 0x9c, - 0xea, 0x9a, 0xd0, 0x49, 0x36, 0x84, 0x12, 0x51, 0xba, 0x66, 0xd9, 0x49, 0x36, 0x30, 0x83, 0xa0, - 0xf7, 0xc2, 0x58, 0xe2, 0x44, 0x2d, 0x6a, 0x6f, 0x6f, 0xb1, 0xcf, 0x2a, 0xce, 0x3a, 0x1f, 0x11, - 0xb8, 0x63, 0xab, 0x29, 0x28, 0xce, 0x60, 0xa3, 0xd7, 0x61, 0x60, 0x83, 0x78, 0x6d, 0x31, 0xe4, - 0x2b, 0xc5, 0xc9, 0x78, 0xf6, 0xae, 0x57, 0x88, 0xd7, 0xe6, 0x12, 0x88, 0xfe, 0xc2, 0x8c, 0x15, - 0x9d, 0x6f, 0xb5, 0xcd, 0x4e, 0x9c, 0x04, 0x6d, 0xf7, 0x0d, 0xe9, 0xe2, 0x7b, 0x7f, 0xc1, 0x8c, - 0xaf, 0x49, 0xfa, 0xdc, 0x97, 0xa2, 0xfe, 0x62, 0xcd, 0x99, 0xf5, 0xa3, 0xe9, 0x46, 0xec, 0x53, - 0xed, 0x08, 0x4f, 0x5d, 0xd1, 0xfd, 0x98, 0x95, 0xf4, 0x79, 0x3f, 0xd4, 0x5f, 0xac, 0x39, 0xa3, - 0x1d, 0x35, 0xef, 0x87, 0x59, 0x1f, 0x6e, 0x14, 0xdc, 0x07, 0x3e, 0xe7, 0x73, 0xe7, 0xff, 0x93, - 0x50, 0x69, 0x6c, 0x38, 0x51, 0x32, 0x3e, 0xc2, 0x26, 0x8d, 0xf2, 0xe9, 0xcc, 0xd0, 0x46, 0xcc, - 0x61, 0xe8, 0x71, 0x28, 0x47, 0x64, 0x9d, 0xc5, 0x6d, 0x1a, 0x11, 0x3d, 0x98, 0xac, 0x63, 0xda, - 0x6e, 0xff, 0x62, 0x29, 0x6d, 0x2e, 0xa5, 0xdf, 0x9b, 0xcf, 0xf6, 0x46, 0x27, 0x8a, 0xa5, 0xdf, - 0xc7, 0x98, 0xed, 0xac, 0x19, 0x4b, 0x38, 0xfa, 0x84, 0x05, 0x43, 0xb7, 0xe3, 0xc0, 0xf7, 0x49, - 0x22, 0x54, 0xd3, 0xcd, 0x82, 0x87, 0xe2, 0x2a, 0xa7, 0xae, 0xfb, 0x20, 0x1a, 0xb0, 0xe4, 0x4b, - 0xbb, 0x4b, 0xb6, 0x1b, 0x5e, 0xa7, 0xd9, 0x15, 0xa4, 0x71, 0x89, 0x37, 0x63, 0x09, 0xa7, 0xa8, - 0xae, 0xcf, 0x51, 0x07, 0xd2, 0xa8, 0xf3, 0xbe, 0x40, 0x15, 0x70, 0xfb, 0xaf, 0x0f, 0xc2, 0xd9, - 0xdc, 0xc5, 0x41, 0x0d, 0x19, 0x66, 0x2a, 0x5c, 0x76, 0x3d, 0x22, 0xc3, 0x93, 0x98, 0x21, 0x73, - 0x53, 0xb5, 0x62, 0x03, 0x03, 0xfd, 0x14, 0x40, 0xe8, 0x44, 0x4e, 0x9b, 0x28, 0xbf, 0xec, 0x91, - 0xed, 0x05, 0xda, 0x8f, 0x65, 0x49, 0x53, 0xef, 0x4d, 0x55, 0x53, 0x8c, 0x0d, 0x96, 0xe8, 0x05, - 0x18, 0x8e, 0x88, 0x47, 0x9c, 0x98, 0x85, 0xfd, 0x66, 0x73, 0x18, 0xb0, 0x06, 0x61, 0x13, 0x0f, - 0x3d, 0xa5, 0x22, 0xb9, 0x32, 0x11, 0x2d, 0xe9, 0x68, 0x2e, 0xf4, 0xa6, 0x05, 0x63, 0xeb, 0xae, - 0x47, 0x34, 0x77, 0x91, 0x71, 0xb0, 0x74, 0xf4, 0x97, 0xbc, 0x6c, 0xd2, 0xd5, 0x12, 0x32, 0xd5, - 0x1c, 0xe3, 0x0c, 0x7b, 0xfa, 0x99, 0xb7, 0x48, 0xc4, 0x44, 0xeb, 0x60, 0xfa, 0x33, 0xdf, 0xe4, - 0xcd, 0x58, 0xc2, 0xd1, 0x34, 0x9c, 0x08, 0x9d, 0x38, 0x9e, 0x89, 0x48, 0x93, 0xf8, 0x89, 0xeb, - 0x78, 0x3c, 0x1f, 0xa0, 0xaa, 0xe3, 0x81, 0x97, 0xd3, 0x60, 0x9c, 0xc5, 0x47, 0x1f, 0x80, 0x47, - 0xb9, 0xe3, 0x63, 0xd1, 0x8d, 0x63, 0xd7, 0x6f, 0xe9, 0x69, 0x20, 0xfc, 0x3f, 0x13, 0x82, 0xd4, - 0xa3, 0xf3, 0xf9, 0x68, 0xb8, 0xd7, 0xf3, 0xe8, 0x19, 0xa8, 0xc6, 0x9b, 0x6e, 0x38, 0x13, 0x35, - 0x63, 0x76, 0xe8, 0x51, 0xd5, 0xde, 0xc6, 0x15, 0xd1, 0x8e, 0x15, 0x06, 0x6a, 0xc0, 0x08, 0xff, - 0x24, 0x3c, 0x14, 0x4d, 0xc8, 0xc7, 0x67, 0x7b, 0xaa, 0x47, 0x91, 0xb2, 0x36, 0x89, 0x9d, 0x3b, - 0x97, 0xe4, 0x11, 0x0c, 0x3f, 0x31, 0xb8, 0x69, 0x90, 0xc1, 0x29, 0xa2, 0xf6, 0x2f, 0x94, 0xd2, - 0x3b, 0x6e, 0x73, 0x91, 0xa2, 0x98, 0x2e, 0xc5, 0xe4, 0xa6, 0x13, 0x49, 0x6f, 0xcc, 0x11, 0xd3, - 0x16, 0x04, 0xdd, 0x9b, 0x4e, 0x64, 0x2e, 0x6a, 0xc6, 0x00, 0x4b, 0x4e, 0xe8, 0x36, 0x0c, 0x24, - 0x9e, 0x53, 0x50, 0x9e, 0x93, 0xc1, 0x51, 0x3b, 0x40, 0x16, 0xa6, 0x63, 0xcc, 0x78, 0xa0, 0xc7, - 0xa8, 0xd5, 0xbf, 0x26, 0x8f, 0x48, 0x84, 0xa1, 0xbe, 0x16, 0x63, 0xd6, 0x6a, 0xff, 0x0a, 0xe4, - 0xc8, 0x55, 0xa5, 0xc8, 0xd0, 0x45, 0x00, 0xba, 0x81, 0x5c, 0x8e, 0xc8, 0xba, 0xbb, 0x2d, 0x0c, - 0x09, 0xb5, 0x76, 0xaf, 0x2b, 0x08, 0x36, 0xb0, 0xe4, 0x33, 0x2b, 0x9d, 0x75, 0xfa, 0x4c, 0xa9, - 0xfb, 0x19, 0x0e, 0xc1, 0x06, 0x16, 0x7a, 0x1e, 0x06, 0xdd, 0xb6, 0xd3, 0x52, 0x21, 0x98, 0x8f, - 0xd1, 0x45, 0x3b, 0xcf, 0x5a, 0xee, 0xed, 0x4e, 0x8c, 0xa9, 0x0e, 0xb1, 0x26, 0x2c, 0x70, 0xd1, - 0x2f, 0x5b, 0x30, 0xd2, 0x08, 0xda, 0xed, 0xc0, 0xe7, 0xdb, 0x2e, 0xb1, 0x87, 0xbc, 0x7d, 0x5c, - 0x6a, 0x7e, 0x72, 0xc6, 0x60, 0xc6, 0x37, 0x91, 0x2a, 0x21, 0xcb, 0x04, 0xe1, 0x54, 0xaf, 0xcc, - 0xb5, 0x5d, 0xd9, 0x67, 0x6d, 0xff, 0xba, 0x05, 0xa7, 0xf8, 0xb3, 0xc6, 0x6e, 0x50, 0xe4, 0x1e, - 0x05, 0xc7, 0xfc, 0x5a, 0x5d, 0x1b, 0x64, 0xe5, 0xa5, 0xeb, 0x82, 0xe3, 0xee, 0x4e, 0xa2, 0x39, - 0x38, 0xb5, 0x1e, 0x44, 0x0d, 0x62, 0x0e, 0x84, 0x10, 0x4c, 0x8a, 0xd0, 0xe5, 0x2c, 0x02, 0xee, - 0x7e, 0x06, 0xdd, 0x84, 0x47, 0x8c, 0x46, 0x73, 0x1c, 0xb8, 0x6c, 0x7a, 0x42, 0x50, 0x7b, 0xe4, - 0x72, 0x2e, 0x16, 0xee, 0xf1, 0x74, 0xda, 0x61, 0x52, 0xeb, 0xc3, 0x61, 0xf2, 0x1a, 0x9c, 0x6b, - 0x74, 0x8f, 0xcc, 0x56, 0xdc, 0x59, 0x8b, 0xb9, 0xa4, 0xaa, 0xd6, 0xbf, 0x4f, 0x10, 0x38, 0x37, - 0xd3, 0x0b, 0x11, 0xf7, 0xa6, 0x81, 0x3e, 0x02, 0xd5, 0x88, 0xb0, 0xaf, 0x12, 0x8b, 0x44, 0x9c, - 0x23, 0xee, 0x92, 0xb5, 0x05, 0xca, 0xc9, 0x6a, 0xd9, 0x2b, 0x1a, 0x62, 0xac, 0x38, 0xa2, 0x3b, - 0x30, 0x14, 0x3a, 0x49, 0x63, 0x43, 0xa4, 0xdf, 0x1c, 0x39, 0xfe, 0x45, 0x31, 0x67, 0x3e, 0x70, - 0x3d, 0xc9, 0x97, 0x39, 0x13, 0x2c, 0xb9, 0x51, 0x6b, 0xa4, 0x11, 0xb4, 0xc3, 0xc0, 0x27, 0x7e, - 0x12, 0x8f, 0x8f, 0x6a, 0x6b, 0x64, 0x46, 0xb5, 0x62, 0x03, 0xe3, 0xfc, 0xfb, 0xe0, 0x54, 0xd7, - 0xc2, 0x3b, 0x90, 0x73, 0x65, 0x16, 0x1e, 0xc9, 0x9f, 0xe2, 0x07, 0x72, 0xb1, 0xfc, 0xe3, 0x4c, - 0x90, 0xab, 0x61, 0xf6, 0xf6, 0xe1, 0xae, 0x73, 0xa0, 0x4c, 0xfc, 0x2d, 0x21, 0xf1, 0x2f, 0x1f, - 0x6d, 0xa4, 0x2f, 0xf9, 0x5b, 0x7c, 0x85, 0x32, 0x9f, 0xc4, 0x25, 0x7f, 0x0b, 0x53, 0xda, 0xe8, - 0x8b, 0x56, 0xca, 0x6c, 0xe3, 0x4e, 0xbe, 0x0f, 0x1d, 0x8b, 0x9d, 0xdf, 0xb7, 0x25, 0x67, 0xff, - 0xdb, 0x12, 0x5c, 0xd8, 0x8f, 0x48, 0x1f, 0xc3, 0xf7, 0x24, 0x0c, 0xc6, 0xec, 0xd8, 0x5a, 0x88, - 0xd0, 0x61, 0x3a, 0xb3, 0xf8, 0x41, 0xf6, 0x6b, 0x58, 0x80, 0x90, 0x07, 0xe5, 0xb6, 0x13, 0x0a, - 0xdf, 0xcf, 0xfc, 0x51, 0xd3, 0x5e, 0xe8, 0x7f, 0xc7, 0x5b, 0x74, 0x42, 0xee, 0x51, 0x30, 0x1a, - 0x30, 0x65, 0x83, 0x12, 0xa8, 0x38, 0x51, 0xe4, 0xc8, 0x33, 0xd2, 0x6b, 0xc5, 0xf0, 0x9b, 0xa6, - 0x24, 0xf9, 0x11, 0x53, 0xaa, 0x09, 0x73, 0x66, 0xf6, 0x67, 0x87, 0x52, 0xa9, 0x1f, 0xec, 0xe0, - 0x3b, 0x86, 0x41, 0xe1, 0xf2, 0xb1, 0x8a, 0xce, 0x36, 0xe2, 0xb9, 0x7b, 0x6c, 0x57, 0x27, 0x32, - 0xa0, 0x05, 0x2b, 0xf4, 0x19, 0x8b, 0xe5, 0x19, 0xcb, 0x74, 0x18, 0xb1, 0x97, 0x3a, 0x9e, 0xb4, - 0x67, 0x33, 0x7b, 0x59, 0x36, 0x62, 0x93, 0x3b, 0xd5, 0xb1, 0x21, 0xcf, 0x98, 0xcb, 0xee, 0xa8, - 0x64, 0x26, 0xb2, 0x84, 0xa3, 0xed, 0x9c, 0x03, 0xee, 0x02, 0x72, 0x55, 0xfb, 0x38, 0xd2, 0xfe, - 0xaa, 0x05, 0xa7, 0xdc, 0xec, 0x49, 0xa5, 0xd8, 0x79, 0x1c, 0x31, 0x84, 0xa2, 0xf7, 0x41, 0xa8, - 0x52, 0xbe, 0x5d, 0x20, 0xdc, 0xdd, 0x19, 0xd4, 0x84, 0x01, 0xd7, 0x5f, 0x0f, 0x84, 0xc9, 0x51, - 0x3f, 0x5a, 0xa7, 0xe6, 0xfd, 0xf5, 0x40, 0xaf, 0x66, 0xfa, 0x0f, 0x33, 0xea, 0x68, 0x01, 0xce, - 0x44, 0xc2, 0x37, 0x74, 0xc5, 0x8d, 0xe9, 0x0e, 0x7e, 0xc1, 0x6d, 0xbb, 0x09, 0x33, 0x17, 0xca, - 0xf5, 0xf1, 0xbb, 0xbb, 0x13, 0x67, 0x70, 0x0e, 0x1c, 0xe7, 0x3e, 0x85, 0xde, 0x80, 0x21, 0x99, - 0x18, 0x5d, 0x2d, 0x62, 0x17, 0xd7, 0x3d, 0xff, 0xd5, 0x64, 0x5a, 0x11, 0x39, 0xd0, 0x92, 0xa1, - 0xfd, 0xe6, 0x30, 0x74, 0x1f, 0x62, 0xa2, 0x8f, 0x42, 0x2d, 0x52, 0xc9, 0xda, 0x56, 0x11, 0xca, - 0x55, 0x7e, 0x5f, 0x71, 0x80, 0xaa, 0x0c, 0x17, 0x9d, 0x96, 0xad, 0x39, 0xd2, 0xed, 0x45, 0xac, - 0xcf, 0x3a, 0x0b, 0x98, 0xdb, 0x82, 0xab, 0x3e, 0xc7, 0xda, 0xf1, 0x1b, 0x98, 0xf1, 0x40, 0x11, - 0x0c, 0x6e, 0x10, 0xc7, 0x4b, 0x36, 0x8a, 0x71, 0xb9, 0x5f, 0x61, 0xb4, 0xb2, 0x29, 0x3b, 0xbc, - 0x15, 0x0b, 0x4e, 0x68, 0x1b, 0x86, 0x36, 0xf8, 0x04, 0x10, 0x16, 0xff, 0xe2, 0x51, 0x07, 0x37, - 0x35, 0xab, 0xf4, 0xe7, 0x16, 0x0d, 0x58, 0xb2, 0x63, 0xd1, 0x31, 0xc6, 0xf9, 0x3d, 0x5f, 0xba, - 0xc5, 0x65, 0x2b, 0xf5, 0x7f, 0x78, 0xff, 0x61, 0x18, 0x89, 0x48, 0x23, 0xf0, 0x1b, 0xae, 0x47, - 0x9a, 0xd3, 0xd2, 0x9d, 0x7e, 0x90, 0x1c, 0x17, 0xb6, 0x6b, 0xc6, 0x06, 0x0d, 0x9c, 0xa2, 0x88, - 0x3e, 0x6d, 0xc1, 0x98, 0xca, 0xf0, 0xa4, 0x1f, 0x84, 0x08, 0xf7, 0xed, 0x42, 0x41, 0xf9, 0xa4, - 0x8c, 0x66, 0x1d, 0xdd, 0xdd, 0x9d, 0x18, 0x4b, 0xb7, 0xe1, 0x0c, 0x5f, 0xf4, 0x0a, 0x40, 0xb0, - 0xc6, 0x43, 0x60, 0xa6, 0x13, 0xe1, 0xcb, 0x3d, 0xc8, 0xab, 0x8e, 0xf1, 0x64, 0x37, 0x49, 0x01, - 0x1b, 0xd4, 0xd0, 0x35, 0x00, 0xbe, 0x6c, 0x56, 0x77, 0x42, 0xb9, 0x2d, 0x90, 0x49, 0x4a, 0xb0, - 0xa2, 0x20, 0xf7, 0x76, 0x27, 0xba, 0x7d, 0x6b, 0x2c, 0xcc, 0xc0, 0x78, 0x1c, 0xfd, 0x24, 0x0c, - 0xc5, 0x9d, 0x76, 0xdb, 0x51, 0x9e, 0xde, 0x02, 0xd3, 0xe7, 0x38, 0x5d, 0x43, 0x14, 0xf1, 0x06, - 0x2c, 0x39, 0xa2, 0xdb, 0x54, 0xa8, 0xc6, 0xc2, 0xe9, 0xc7, 0x56, 0x11, 0xb7, 0x09, 0x86, 0xd9, - 0x3b, 0xbd, 0x47, 0x46, 0xf4, 0xe0, 0x1c, 0x9c, 0x7b, 0xbb, 0x13, 0x8f, 0xa4, 0xdb, 0x17, 0x02, - 0x91, 0xd0, 0x96, 0x4b, 0x13, 0x5d, 0x95, 0x75, 0x52, 0xe8, 0x6b, 0xcb, 0xf4, 0xfd, 0xa7, 0x75, - 0x9d, 0x14, 0xd6, 0xdc, 0x7b, 0xcc, 0xcc, 0x87, 0xd1, 0x22, 0x9c, 0x6e, 0x04, 0x7e, 0x12, 0x05, - 0x9e, 0xc7, 0x8b, 0xff, 0xf0, 0x1d, 0x1a, 0xf7, 0x04, 0xbf, 0x53, 0x74, 0xfb, 0xf4, 0x4c, 0x37, - 0x0a, 0xce, 0x7b, 0xce, 0xf6, 0xd3, 0xb1, 0x81, 0x62, 0x70, 0x9e, 0x87, 0x11, 0xb2, 0x9d, 0x90, - 0xc8, 0x77, 0xbc, 0x1b, 0x78, 0x41, 0xfa, 0x40, 0xd9, 0x1a, 0xb8, 0x64, 0xb4, 0xe3, 0x14, 0x16, - 0xb2, 0x95, 0x5b, 0xc2, 0x48, 0xd2, 0xe4, 0x6e, 0x09, 0xe9, 0x84, 0xb0, 0xff, 0x77, 0x29, 0x65, - 0x90, 0xad, 0x46, 0x84, 0xa0, 0x00, 0x2a, 0x7e, 0xd0, 0x54, 0xb2, 0xff, 0x6a, 0x31, 0xb2, 0xff, - 0x7a, 0xd0, 0x34, 0x8a, 0xa9, 0xd0, 0x7f, 0x31, 0xe6, 0x7c, 0x58, 0xb5, 0x09, 0x59, 0x96, 0x83, - 0x01, 0xc4, 0x46, 0xa3, 0x48, 0xce, 0xaa, 0xda, 0xc4, 0x92, 0xc9, 0x08, 0xa7, 0xf9, 0xa2, 0x4d, - 0xa8, 0x6c, 0x04, 0x71, 0x22, 0xb7, 0x1f, 0x47, 0xdc, 0xe9, 0x5c, 0x09, 0xe2, 0x84, 0x59, 0x11, - 0xea, 0xb5, 0x69, 0x4b, 0x8c, 0x39, 0x0f, 0xfb, 0xbf, 0x58, 0x29, 0x8f, 0xf7, 0x2d, 0x16, 0x27, - 0xbb, 0x45, 0x7c, 0xba, 0xac, 0xcd, 0xc0, 0xa0, 0x1f, 0xce, 0x64, 0x1d, 0xbe, 0xab, 0x57, 0x69, - 0xab, 0x3b, 0x94, 0xc2, 0x24, 0x23, 0x61, 0xc4, 0x10, 0x7d, 0xdc, 0x4a, 0xe7, 0x7f, 0x96, 0x8a, - 0xd8, 0x60, 0x98, 0x39, 0xd0, 0xfb, 0xa6, 0x92, 0xda, 0x5f, 0xb4, 0x60, 0xa8, 0xee, 0x34, 0x36, - 0x83, 0xf5, 0x75, 0xf4, 0x0c, 0x54, 0x9b, 0x9d, 0xc8, 0x4c, 0x45, 0x55, 0xdb, 0xfc, 0x59, 0xd1, - 0x8e, 0x15, 0x06, 0x9d, 0xc3, 0xeb, 0x4e, 0x43, 0x66, 0x42, 0x97, 0xf9, 0x1c, 0xbe, 0xcc, 0x5a, - 0xb0, 0x80, 0xa0, 0x17, 0x60, 0xb8, 0xed, 0x6c, 0xcb, 0x87, 0xb3, 0xee, 0xf6, 0x45, 0x0d, 0xc2, - 0x26, 0x9e, 0xfd, 0x2f, 0x2d, 0x18, 0xaf, 0x3b, 0xb1, 0xdb, 0x98, 0xee, 0x24, 0x1b, 0x75, 0x37, - 0x59, 0xeb, 0x34, 0x36, 0x49, 0xc2, 0xd3, 0xdf, 0x69, 0x2f, 0x3b, 0x31, 0x5d, 0x4a, 0x6a, 0x5f, - 0xa7, 0x7a, 0x79, 0x43, 0xb4, 0x63, 0x85, 0x81, 0xde, 0x80, 0xe1, 0xd0, 0x89, 0xe3, 0x3b, 0x41, - 0xd4, 0xc4, 0x64, 0xbd, 0x98, 0xe2, 0x13, 0x2b, 0xa4, 0x11, 0x91, 0x04, 0x93, 0x75, 0x71, 0x24, - 0xac, 0xe9, 0x63, 0x93, 0x99, 0xfd, 0x79, 0x0b, 0xce, 0xd5, 0x89, 0x13, 0x91, 0x88, 0xd5, 0xaa, - 0x50, 0x2f, 0x32, 0xe3, 0x05, 0x9d, 0x26, 0x7a, 0x1d, 0xaa, 0x09, 0x6d, 0xa6, 0xdd, 0xb2, 0x8a, - 0xed, 0x16, 0x3b, 0xd1, 0x5d, 0x15, 0xc4, 0xb1, 0x62, 0x63, 0xff, 0x0d, 0x0b, 0x46, 0xd8, 0xe1, - 0xd8, 0x2c, 0x49, 0x1c, 0xd7, 0xeb, 0x2a, 0xe9, 0x64, 0xf5, 0x59, 0xd2, 0xe9, 0x02, 0x0c, 0x6c, - 0x04, 0x6d, 0x92, 0x3d, 0xd8, 0xbd, 0x12, 0xd0, 0x6d, 0x35, 0x85, 0xa0, 0xe7, 0xe8, 0x87, 0x77, - 0xfd, 0xc4, 0xa1, 0x4b, 0x40, 0x3a, 0x5f, 0x4f, 0xf0, 0x8f, 0xae, 0x9a, 0xb1, 0x89, 0x63, 0xff, - 0x56, 0x0d, 0x86, 0xc4, 0xe9, 0x7f, 0xdf, 0x25, 0x10, 0xe4, 0xfe, 0xbe, 0xd4, 0x73, 0x7f, 0x1f, - 0xc3, 0x60, 0x83, 0x15, 0x8c, 0x13, 0x66, 0xe4, 0xb5, 0x42, 0xc2, 0x45, 0x78, 0x0d, 0x3a, 0xdd, - 0x2d, 0xfe, 0x1f, 0x0b, 0x56, 0xe8, 0x0b, 0x16, 0x9c, 0x68, 0x04, 0xbe, 0x4f, 0x1a, 0xda, 0xc6, - 0x19, 0x28, 0x22, 0x2a, 0x60, 0x26, 0x4d, 0x54, 0x9f, 0xcc, 0x64, 0x00, 0x38, 0xcb, 0x1e, 0xbd, - 0x04, 0xa3, 0x7c, 0xcc, 0x6e, 0xa6, 0x3c, 0xc6, 0xba, 0xd2, 0x8f, 0x09, 0xc4, 0x69, 0x5c, 0x34, - 0xc9, 0x3d, 0xef, 0xa2, 0xa6, 0xce, 0xa0, 0x76, 0xac, 0x19, 0xd5, 0x74, 0x0c, 0x0c, 0x14, 0x01, - 0x8a, 0xc8, 0x7a, 0x44, 0xe2, 0x0d, 0x11, 0x1d, 0xc1, 0xec, 0xab, 0xa1, 0xc3, 0xa5, 0x4b, 0xe3, - 0x2e, 0x4a, 0x38, 0x87, 0x3a, 0xda, 0x14, 0x1b, 0xcc, 0x6a, 0x11, 0x32, 0x54, 0x7c, 0xe6, 0x9e, - 0xfb, 0xcc, 0x09, 0xa8, 0xc4, 0x1b, 0x4e, 0xd4, 0x64, 0x76, 0x5d, 0x99, 0xa7, 0xe8, 0xac, 0xd0, - 0x06, 0xcc, 0xdb, 0xd1, 0x2c, 0x9c, 0xcc, 0xd4, 0x29, 0x8a, 0x85, 0x67, 0x57, 0xa5, 0x63, 0x64, - 0x2a, 0x1c, 0xc5, 0xb8, 0xeb, 0x09, 0xd3, 0xf9, 0x30, 0xbc, 0x8f, 0xf3, 0x61, 0x47, 0xc5, 0xe0, - 0x71, 0x9f, 0xeb, 0xcb, 0x85, 0x0c, 0x40, 0x5f, 0x01, 0x77, 0x9f, 0xcb, 0x04, 0xdc, 0x8d, 0xb2, - 0x0e, 0xdc, 0x2c, 0xa6, 0x03, 0x07, 0x8f, 0xae, 0x7b, 0x90, 0xd1, 0x72, 0x7f, 0x6e, 0x81, 0xfc, - 0xae, 0x33, 0x4e, 0x63, 0x83, 0xd0, 0x29, 0x83, 0xde, 0x0b, 0x63, 0x6a, 0x0b, 0x3d, 0x13, 0x74, - 0x7c, 0x1e, 0x28, 0x57, 0xd6, 0x47, 0xb8, 0x38, 0x05, 0xc5, 0x19, 0x6c, 0x34, 0x05, 0x35, 0x3a, - 0x4e, 0xfc, 0x51, 0xae, 0x6b, 0xd5, 0x36, 0x7d, 0x7a, 0x79, 0x5e, 0x3c, 0xa5, 0x71, 0x50, 0x00, - 0xa7, 0x3c, 0x27, 0x4e, 0x58, 0x0f, 0xe8, 0x8e, 0xfa, 0x90, 0xc5, 0x0a, 0x58, 0xcc, 0xff, 0x42, - 0x96, 0x10, 0xee, 0xa6, 0x6d, 0x7f, 0x6b, 0x00, 0x46, 0x53, 0x92, 0xf1, 0x80, 0x4a, 0xfa, 0x19, - 0xa8, 0x4a, 0xbd, 0x99, 0x2d, 0xab, 0xa2, 0x94, 0xab, 0xc2, 0xa0, 0x4a, 0x6b, 0x4d, 0x6b, 0xd5, - 0xac, 0x51, 0x61, 0x28, 0x5c, 0x6c, 0xe2, 0x31, 0xa1, 0x9c, 0x78, 0xf1, 0x8c, 0xe7, 0x12, 0x3f, - 0xe1, 0xdd, 0x2c, 0x46, 0x28, 0xaf, 0x2e, 0xac, 0x98, 0x44, 0xb5, 0x50, 0xce, 0x00, 0x70, 0x96, - 0x3d, 0xfa, 0x94, 0x05, 0xa3, 0xce, 0x9d, 0x58, 0x57, 0x35, 0x15, 0xa1, 0x75, 0x47, 0x54, 0x52, - 0xa9, 0x42, 0xa9, 0xdc, 0xe5, 0x9b, 0x6a, 0xc2, 0x69, 0xa6, 0xe8, 0x2d, 0x0b, 0x10, 0xd9, 0x26, - 0x0d, 0x19, 0xfc, 0x27, 0xfa, 0x32, 0x58, 0xc4, 0x4e, 0xf3, 0x52, 0x17, 0x5d, 0x2e, 0xd5, 0xbb, - 0xdb, 0x71, 0x4e, 0x1f, 0xec, 0x7f, 0x56, 0x56, 0x0b, 0x4a, 0xc7, 0x9b, 0x3a, 0x46, 0xdc, 0x9b, - 0x75, 0xf8, 0xb8, 0x37, 0x1d, 0x3f, 0xd0, 0x9d, 0x03, 0x99, 0x4a, 0x99, 0x2a, 0x3d, 0xa0, 0x94, - 0xa9, 0x9f, 0xb6, 0x52, 0x05, 0x84, 0x86, 0x2f, 0xbe, 0x52, 0x6c, 0xac, 0xeb, 0x24, 0x8f, 0x6d, - 0xc8, 0x48, 0xf7, 0x74, 0x48, 0x0b, 0x95, 0xa6, 0x06, 0xda, 0x81, 0xa4, 0xe1, 0xbf, 0x2f, 0xc3, - 0xb0, 0xa1, 0x49, 0x73, 0xcd, 0x22, 0xeb, 0x21, 0x33, 0x8b, 0x4a, 0x07, 0x30, 0x8b, 0x7e, 0x0a, - 0x6a, 0x0d, 0x29, 0xe5, 0x8b, 0x29, 0xa1, 0x9b, 0xd5, 0x1d, 0x5a, 0xd0, 0xab, 0x26, 0xac, 0x79, - 0xa2, 0xb9, 0x54, 0xa2, 0x8d, 0xd0, 0x10, 0x03, 0x4c, 0x43, 0xe4, 0x65, 0xc2, 0x08, 0x4d, 0xd1, - 0xfd, 0x0c, 0xab, 0x33, 0x15, 0xba, 0xe2, 0xbd, 0x64, 0x44, 0x3a, 0xaf, 0x33, 0xb5, 0x3c, 0x2f, - 0x9b, 0xb1, 0x89, 0x63, 0x7f, 0xcb, 0x52, 0x1f, 0xf7, 0x3e, 0x54, 0x54, 0xb8, 0x9d, 0xae, 0xa8, - 0x70, 0xa9, 0x90, 0x61, 0xee, 0x51, 0x4a, 0xe1, 0x3a, 0x0c, 0xcd, 0x04, 0xed, 0xb6, 0xe3, 0x37, - 0xd1, 0xf7, 0xc3, 0x50, 0x83, 0xff, 0x14, 0x8e, 0x1d, 0x76, 0x3c, 0x28, 0xa0, 0x58, 0xc2, 0xd0, - 0x63, 0x30, 0xe0, 0x44, 0x2d, 0xe9, 0xcc, 0x61, 0xa1, 0x30, 0xd3, 0x51, 0x2b, 0xc6, 0xac, 0xd5, - 0xfe, 0x47, 0x03, 0xc0, 0x4e, 0xa0, 0x9d, 0x88, 0x34, 0x57, 0x03, 0x56, 0xc2, 0xef, 0x58, 0x0f, - 0xd5, 0xf4, 0x66, 0xe9, 0x61, 0x3e, 0x58, 0x33, 0x0e, 0x57, 0xca, 0xf7, 0xf9, 0x70, 0xa5, 0xc7, - 0x79, 0xd9, 0xc0, 0x43, 0x74, 0x5e, 0x66, 0x7f, 0xd6, 0x02, 0xa4, 0xc2, 0x16, 0xf4, 0x81, 0xf6, - 0x14, 0xd4, 0x54, 0x00, 0x83, 0x30, 0xac, 0xb4, 0x88, 0x90, 0x00, 0xac, 0x71, 0xfa, 0xd8, 0x21, - 0x3f, 0x29, 0xe5, 0x77, 0x39, 0x1d, 0x45, 0xcb, 0xa4, 0xbe, 0x10, 0xe7, 0xf6, 0x6f, 0x97, 0xe0, - 0x11, 0xae, 0x92, 0x17, 0x1d, 0xdf, 0x69, 0x91, 0x36, 0xed, 0x55, 0xbf, 0x21, 0x0a, 0x0d, 0xba, - 0x35, 0x73, 0x65, 0x54, 0xec, 0x51, 0xd7, 0x2e, 0x5f, 0x73, 0x7c, 0x95, 0xcd, 0xfb, 0x6e, 0x82, - 0x19, 0x71, 0x14, 0x43, 0x55, 0xd6, 0x8c, 0x17, 0xb2, 0xb8, 0x20, 0x46, 0x4a, 0x2c, 0x09, 0xbd, - 0x49, 0xb0, 0x62, 0x44, 0x0d, 0x57, 0x2f, 0x68, 0x6c, 0x62, 0x12, 0x06, 0x4c, 0xee, 0x1a, 0x41, - 0x89, 0x0b, 0xa2, 0x1d, 0x2b, 0x0c, 0xfb, 0xb7, 0x2d, 0xc8, 0x6a, 0x24, 0xa3, 0x56, 0x9a, 0xb5, - 0x67, 0xad, 0xb4, 0x03, 0x14, 0x2b, 0xfb, 0x09, 0x18, 0x76, 0x12, 0x6a, 0x44, 0xf0, 0x6d, 0x77, - 0xf9, 0x70, 0xc7, 0x1a, 0x8b, 0x41, 0xd3, 0x5d, 0x77, 0xd9, 0x76, 0xdb, 0x24, 0x67, 0xff, 0x8f, - 0x01, 0x38, 0xd5, 0x95, 0xbb, 0x81, 0x5e, 0x84, 0x91, 0x86, 0x98, 0x1e, 0xa1, 0x74, 0x68, 0xd5, - 0xcc, 0x20, 0x36, 0x0d, 0xc3, 0x29, 0xcc, 0x3e, 0x26, 0xe8, 0x3c, 0x9c, 0x8e, 0xe8, 0x46, 0xbf, - 0x43, 0xa6, 0xd7, 0x13, 0x12, 0xad, 0x90, 0x46, 0xe0, 0x37, 0x79, 0x45, 0xbf, 0x72, 0xfd, 0xd1, - 0xbb, 0xbb, 0x13, 0xa7, 0x71, 0x37, 0x18, 0xe7, 0x3d, 0x83, 0x42, 0x18, 0xf5, 0x4c, 0x1b, 0x50, - 0x6c, 0x00, 0x0e, 0x65, 0x3e, 0x2a, 0x1b, 0x21, 0xd5, 0x8c, 0xd3, 0x0c, 0xd2, 0x86, 0x64, 0xe5, - 0x01, 0x19, 0x92, 0x9f, 0xd4, 0x86, 0x24, 0x3f, 0x7f, 0xff, 0x60, 0xc1, 0xb9, 0x3b, 0xc7, 0x6d, - 0x49, 0xbe, 0x0c, 0x55, 0x19, 0x9b, 0xd4, 0x57, 0x4c, 0x8f, 0x49, 0xa7, 0x87, 0x44, 0xbb, 0x57, - 0x82, 0x9c, 0x4d, 0x08, 0x5d, 0x67, 0x5a, 0xe3, 0xa7, 0xd6, 0xd9, 0xc1, 0xb4, 0x3e, 0xda, 0xe6, - 0x71, 0x59, 0x5c, 0xb7, 0x7d, 0xa0, 0xe8, 0x4d, 0x94, 0x0e, 0xd5, 0x52, 0x29, 0x0d, 0x2a, 0x5c, - 0xeb, 0x22, 0x80, 0x36, 0xd4, 0x44, 0xc0, 0xba, 0x3a, 0xf6, 0xd5, 0xf6, 0x1c, 0x36, 0xb0, 0xe8, - 0x9e, 0xda, 0xf5, 0xe3, 0xc4, 0xf1, 0xbc, 0x2b, 0xae, 0x9f, 0x08, 0xe7, 0xa0, 0x52, 0xe2, 0xf3, - 0x1a, 0x84, 0x4d, 0xbc, 0xf3, 0xef, 0x31, 0xbe, 0xcb, 0x41, 0xbe, 0xe7, 0x06, 0x9c, 0x9b, 0x73, - 0x13, 0x95, 0x66, 0xa1, 0xe6, 0x11, 0xb5, 0xc3, 0x54, 0xda, 0x90, 0xd5, 0x33, 0x6d, 0xc8, 0x48, - 0x73, 0x28, 0xa5, 0xb3, 0x32, 0xb2, 0x69, 0x0e, 0xf6, 0x8b, 0x70, 0x66, 0xce, 0x4d, 0x2e, 0xbb, - 0x1e, 0x39, 0x20, 0x13, 0xfb, 0x37, 0x07, 0x61, 0xc4, 0x4c, 0xd4, 0x3b, 0x48, 0xe6, 0xd3, 0xe7, - 0xa9, 0xa9, 0x25, 0xde, 0xce, 0x55, 0x87, 0x66, 0xb7, 0x8e, 0x9c, 0x35, 0x98, 0x3f, 0x62, 0x86, - 0xb5, 0xa5, 0x79, 0x62, 0xb3, 0x03, 0xe8, 0x0e, 0x54, 0xd6, 0x59, 0x18, 0x7e, 0xb9, 0x88, 0xc8, - 0x82, 0xbc, 0x11, 0xd5, 0xcb, 0x8c, 0x07, 0xf2, 0x73, 0x7e, 0x54, 0x43, 0x46, 0xe9, 0xdc, 0x2e, - 0x23, 0x74, 0x54, 0x64, 0x75, 0x29, 0x8c, 0x5e, 0xa2, 0xbe, 0x72, 0x08, 0x51, 0x9f, 0x12, 0xbc, - 0x83, 0x0f, 0x48, 0xf0, 0xb2, 0x94, 0x8a, 0x64, 0x83, 0xd9, 0x6f, 0x22, 0xd6, 0x7d, 0x88, 0x0d, - 0x82, 0x91, 0x52, 0x91, 0x02, 0xe3, 0x2c, 0x3e, 0xfa, 0x98, 0x12, 0xdd, 0xd5, 0x22, 0xfc, 0xaa, - 0xe6, 0x8c, 0x3e, 0x6e, 0xa9, 0xfd, 0xd9, 0x12, 0x8c, 0xcd, 0xf9, 0x9d, 0xe5, 0xb9, 0xe5, 0xce, - 0x9a, 0xe7, 0x36, 0xae, 0x91, 0x1d, 0x2a, 0x9a, 0x37, 0xc9, 0xce, 0xfc, 0xac, 0x58, 0x41, 0x6a, - 0xce, 0x5c, 0xa3, 0x8d, 0x98, 0xc3, 0xa8, 0x30, 0x5a, 0x77, 0xfd, 0x16, 0x89, 0xc2, 0xc8, 0x15, - 0x2e, 0x4f, 0x43, 0x18, 0x5d, 0xd6, 0x20, 0x6c, 0xe2, 0x51, 0xda, 0xc1, 0x1d, 0x9f, 0x44, 0x59, - 0x43, 0x76, 0x89, 0x36, 0x62, 0x0e, 0xa3, 0x48, 0x49, 0xd4, 0x89, 0x13, 0x31, 0x19, 0x15, 0xd2, - 0x2a, 0x6d, 0xc4, 0x1c, 0x46, 0x57, 0x7a, 0xdc, 0x59, 0x63, 0x81, 0x1b, 0x99, 0xc0, 0xfa, 0x15, - 0xde, 0x8c, 0x25, 0x9c, 0xa2, 0x6e, 0x92, 0x9d, 0x59, 0xba, 0xeb, 0xcd, 0xe4, 0xd7, 0x5c, 0xe3, - 0xcd, 0x58, 0xc2, 0x59, 0x29, 0xc2, 0xf4, 0x70, 0x7c, 0xd7, 0x95, 0x22, 0x4c, 0x77, 0xbf, 0xc7, - 0xfe, 0xf9, 0x97, 0x2c, 0x18, 0x31, 0xc3, 0xad, 0x50, 0x2b, 0x63, 0xe3, 0x2e, 0x75, 0x55, 0xb2, - 0xfd, 0xb1, 0xbc, 0xab, 0xbd, 0x5a, 0x6e, 0x12, 0x84, 0xf1, 0xb3, 0xc4, 0x6f, 0xb9, 0x3e, 0x61, - 0xa7, 0xe8, 0x3c, 0x4c, 0x2b, 0x15, 0xcb, 0x35, 0x13, 0x34, 0xc9, 0x21, 0x8c, 0x64, 0xfb, 0x16, - 0x9c, 0xea, 0x4a, 0xaa, 0xea, 0xc3, 0xb4, 0xd8, 0x37, 0xa5, 0xd5, 0xc6, 0x30, 0x4c, 0x09, 0xcb, - 0x72, 0x38, 0x33, 0x70, 0x8a, 0x2f, 0x24, 0xca, 0x69, 0xa5, 0xb1, 0x41, 0xda, 0x2a, 0x51, 0x8e, - 0xf9, 0xd7, 0x6f, 0x66, 0x81, 0xb8, 0x1b, 0xdf, 0xfe, 0x9c, 0x05, 0xa3, 0xa9, 0x3c, 0xb7, 0x82, - 0x8c, 0x20, 0xb6, 0xd2, 0x02, 0x16, 0xfd, 0xc7, 0x42, 0xa0, 0xcb, 0x4c, 0x99, 0xea, 0x95, 0xa6, - 0x41, 0xd8, 0xc4, 0xb3, 0xbf, 0x58, 0x82, 0xaa, 0x8c, 0xa0, 0xe8, 0xa3, 0x2b, 0x9f, 0xb1, 0x60, - 0x54, 0x9d, 0x69, 0x30, 0x67, 0x59, 0xa9, 0x88, 0xa4, 0x04, 0xda, 0x03, 0xb5, 0xdd, 0xf6, 0xd7, - 0x03, 0x6d, 0x91, 0x63, 0x93, 0x19, 0x4e, 0xf3, 0x46, 0x37, 0x01, 0xe2, 0x9d, 0x38, 0x21, 0x6d, - 0xc3, 0x6d, 0x67, 0x1b, 0x2b, 0x6e, 0xb2, 0x11, 0x44, 0x84, 0xae, 0xaf, 0xeb, 0x41, 0x93, 0xac, - 0x28, 0x4c, 0x6d, 0x42, 0xe9, 0x36, 0x6c, 0x50, 0xb2, 0xff, 0x41, 0x09, 0x4e, 0x66, 0xbb, 0x84, - 0x3e, 0x08, 0x23, 0x92, 0xbb, 0x71, 0x4d, 0x99, 0x0c, 0x1b, 0x19, 0xc1, 0x06, 0xec, 0xde, 0xee, - 0xc4, 0x44, 0xf7, 0x35, 0x71, 0x93, 0x26, 0x0a, 0x4e, 0x11, 0xe3, 0x07, 0x4b, 0xe2, 0x04, 0xb4, - 0xbe, 0x33, 0x1d, 0x86, 0xe2, 0x74, 0xc8, 0x38, 0x58, 0x32, 0xa1, 0x38, 0x83, 0x8d, 0x96, 0xe1, - 0x8c, 0xd1, 0x72, 0x9d, 0xb8, 0xad, 0x8d, 0xb5, 0x20, 0x92, 0x3b, 0xab, 0xc7, 0x74, 0x60, 0x57, - 0x37, 0x0e, 0xce, 0x7d, 0x92, 0x6a, 0xfb, 0x86, 0x13, 0x3a, 0x0d, 0x37, 0xd9, 0x11, 0x7e, 0x48, - 0x25, 0x9b, 0x66, 0x44, 0x3b, 0x56, 0x18, 0xf6, 0x22, 0x0c, 0xf4, 0x39, 0x83, 0xfa, 0xb2, 0xe8, - 0x5f, 0x86, 0x2a, 0x25, 0x27, 0xcd, 0xbb, 0x22, 0x48, 0x06, 0x50, 0x95, 0x37, 0x8d, 0x20, 0x1b, - 0xca, 0xae, 0x23, 0xcf, 0xee, 0xd4, 0x6b, 0xcd, 0xc7, 0x71, 0x87, 0x6d, 0x92, 0x29, 0x10, 0x3d, - 0x09, 0x65, 0xb2, 0x1d, 0x66, 0x0f, 0xe9, 0x2e, 0x6d, 0x87, 0x6e, 0x44, 0x62, 0x8a, 0x44, 0xb6, - 0x43, 0x74, 0x1e, 0x4a, 0x6e, 0x53, 0x28, 0x29, 0x10, 0x38, 0xa5, 0xf9, 0x59, 0x5c, 0x72, 0x9b, - 0xf6, 0x36, 0xd4, 0xd4, 0xd5, 0x26, 0x68, 0x53, 0xca, 0x6e, 0xab, 0x88, 0x90, 0x27, 0x49, 0xb7, - 0x87, 0xd4, 0xee, 0x00, 0xe8, 0x84, 0xbf, 0xa2, 0xe4, 0xcb, 0x05, 0x18, 0x68, 0x04, 0x22, 0x19, - 0xb9, 0xaa, 0xc9, 0x30, 0xa1, 0xcd, 0x20, 0xf6, 0x2d, 0x18, 0xbb, 0xe6, 0x07, 0x77, 0x58, 0x5d, - 0x76, 0x56, 0x86, 0x8c, 0x12, 0x5e, 0xa7, 0x3f, 0xb2, 0x26, 0x02, 0x83, 0x62, 0x0e, 0x53, 0xf5, - 0x99, 0x4a, 0xbd, 0xea, 0x33, 0xd9, 0x1f, 0xb7, 0x60, 0x44, 0x65, 0x0e, 0xcd, 0x6d, 0x6d, 0x52, - 0xba, 0xad, 0x28, 0xe8, 0x84, 0x59, 0xba, 0xec, 0xf2, 0x21, 0xcc, 0x61, 0x66, 0x4a, 0x5d, 0x69, - 0x9f, 0x94, 0xba, 0x0b, 0x30, 0xb0, 0xe9, 0xfa, 0xcd, 0xec, 0x6d, 0x1a, 0xd7, 0x5c, 0xbf, 0x89, - 0x19, 0x84, 0x76, 0xe1, 0xa4, 0xea, 0x82, 0x54, 0x08, 0x2f, 0xc2, 0xc8, 0x5a, 0xc7, 0xf5, 0x9a, - 0xb2, 0xbe, 0x5a, 0xc6, 0x53, 0x52, 0x37, 0x60, 0x38, 0x85, 0x49, 0xf7, 0x75, 0x6b, 0xae, 0xef, - 0x44, 0x3b, 0xcb, 0x5a, 0x03, 0x29, 0xa1, 0x54, 0x57, 0x10, 0x6c, 0x60, 0xd9, 0x6f, 0x96, 0x61, - 0x2c, 0x9d, 0x3f, 0xd5, 0xc7, 0xf6, 0xea, 0x49, 0xa8, 0xb0, 0x94, 0xaa, 0xec, 0xa7, 0xe5, 0x25, - 0xc9, 0x38, 0x0c, 0xc5, 0x30, 0xc8, 0x8b, 0x31, 0x14, 0x73, 0x13, 0x8d, 0xea, 0xa4, 0xf2, 0xaf, - 0xb0, 0x78, 0x32, 0x51, 0xff, 0x41, 0xb0, 0x42, 0x9f, 0xb2, 0x60, 0x28, 0x08, 0xcd, 0xba, 0x3e, - 0x1f, 0x28, 0x32, 0xb7, 0x4c, 0x24, 0xcb, 0x08, 0x8b, 0x58, 0x7d, 0x7a, 0xf9, 0x39, 0x24, 0xeb, - 0xf3, 0x3f, 0x02, 0x23, 0x26, 0xe6, 0x7e, 0x46, 0x71, 0xd5, 0x34, 0x8a, 0x3f, 0x63, 0x4e, 0x0a, - 0x91, 0x3d, 0xd7, 0xc7, 0x72, 0xbb, 0x01, 0x95, 0x86, 0x0a, 0x00, 0x38, 0x54, 0x55, 0x4e, 0x55, - 0x1d, 0x81, 0x1d, 0x02, 0x71, 0x6a, 0xf6, 0xb7, 0x2c, 0x63, 0x7e, 0x60, 0x12, 0xcf, 0x37, 0x51, - 0x04, 0xe5, 0xd6, 0xd6, 0xa6, 0x30, 0x45, 0xaf, 0x16, 0x34, 0xbc, 0x73, 0x5b, 0x9b, 0x7a, 0x8e, - 0x9b, 0xad, 0x98, 0x32, 0xeb, 0xc3, 0x09, 0x98, 0x4a, 0xb2, 0x2c, 0xef, 0x9f, 0x64, 0x69, 0xbf, - 0x55, 0x82, 0x53, 0x5d, 0x93, 0x0a, 0xbd, 0x01, 0x95, 0x88, 0xbe, 0xa5, 0x78, 0xbd, 0x85, 0xc2, - 0xd2, 0x22, 0xe3, 0xf9, 0xa6, 0xd6, 0xbb, 0xe9, 0x76, 0xcc, 0x59, 0xa2, 0xab, 0x80, 0x74, 0x98, - 0x8a, 0xf2, 0x40, 0xf2, 0x57, 0x3e, 0x2f, 0x1e, 0x45, 0xd3, 0x5d, 0x18, 0x38, 0xe7, 0x29, 0xf4, - 0x52, 0xd6, 0x91, 0x59, 0x4e, 0x9f, 0x5b, 0xee, 0xe5, 0x93, 0xb4, 0xff, 0x79, 0x09, 0x46, 0x53, - 0x65, 0x96, 0x90, 0x07, 0x55, 0xe2, 0x31, 0xa7, 0xbe, 0x54, 0x36, 0x47, 0xad, 0x5a, 0xac, 0x14, - 0xe4, 0x25, 0x41, 0x17, 0x2b, 0x0e, 0x0f, 0xc7, 0xe1, 0xfa, 0x8b, 0x30, 0x22, 0x3b, 0xf4, 0x01, - 0xa7, 0xed, 0x89, 0x01, 0x54, 0x73, 0xf4, 0x92, 0x01, 0xc3, 0x29, 0x4c, 0xfb, 0x77, 0xca, 0x30, - 0xce, 0x4f, 0x41, 0x9a, 0x6a, 0xe6, 0x2d, 0xca, 0xfd, 0xd6, 0x5f, 0xd1, 0xc5, 0xd0, 0xf8, 0x40, - 0xae, 0x1d, 0xf5, 0x92, 0x80, 0x7c, 0x46, 0x7d, 0x45, 0x66, 0x7d, 0x25, 0x13, 0x99, 0xc5, 0xcd, - 0xee, 0xd6, 0x31, 0xf5, 0xe8, 0xbb, 0x2b, 0x54, 0xeb, 0x57, 0x4a, 0x70, 0x22, 0x73, 0x03, 0x03, - 0x7a, 0x33, 0x5d, 0xb4, 0xd7, 0x2a, 0xc2, 0x57, 0xbe, 0x67, 0x51, 0xfe, 0x83, 0x95, 0xee, 0x7d, - 0x40, 0x4b, 0xc5, 0xfe, 0x83, 0x12, 0x8c, 0xa5, 0xaf, 0x8e, 0x78, 0x08, 0x47, 0xea, 0xdd, 0x50, - 0x63, 0xd5, 0xd1, 0xd9, 0x95, 0x98, 0xdc, 0x25, 0xcf, 0x0b, 0x51, 0xcb, 0x46, 0xac, 0xe1, 0x0f, - 0x45, 0x45, 0x64, 0xfb, 0xef, 0x59, 0x70, 0x96, 0xbf, 0x65, 0x76, 0x1e, 0xfe, 0xd5, 0xbc, 0xd1, - 0x7d, 0xb5, 0xd8, 0x0e, 0x66, 0x8a, 0xf8, 0xed, 0x37, 0xbe, 0xec, 0x2a, 0x3e, 0xd1, 0xdb, 0xf4, - 0x54, 0x78, 0x08, 0x3b, 0x7b, 0xa0, 0xc9, 0x60, 0xff, 0x41, 0x19, 0xf4, 0xed, 0x83, 0xc8, 0x15, - 0x39, 0x8e, 0x85, 0x14, 0x33, 0x5c, 0xd9, 0xf1, 0x1b, 0xfa, 0x9e, 0xc3, 0x6a, 0x26, 0xc5, 0xf1, - 0xe7, 0x2c, 0x18, 0x76, 0x7d, 0x37, 0x71, 0x1d, 0xb6, 0x8d, 0x2e, 0xe6, 0x66, 0x34, 0xc5, 0x6e, - 0x9e, 0x53, 0x0e, 0x22, 0xf3, 0x1c, 0x47, 0x31, 0xc3, 0x26, 0x67, 0xf4, 0x61, 0x11, 0x3c, 0x5d, - 0x2e, 0x2c, 0x3b, 0xb7, 0x9a, 0x89, 0x98, 0x0e, 0xa9, 0xe1, 0x95, 0x44, 0x05, 0x25, 0xb5, 0x63, - 0x4a, 0x4a, 0xd5, 0xc5, 0xd5, 0xf7, 0x40, 0xd3, 0x66, 0xcc, 0x19, 0xd9, 0x31, 0xa0, 0xee, 0xb1, - 0x38, 0x60, 0x60, 0xea, 0x14, 0xd4, 0x9c, 0x4e, 0x12, 0xb4, 0xe9, 0x30, 0x89, 0xa3, 0x26, 0x1d, - 0x7a, 0x2b, 0x01, 0x58, 0xe3, 0xd8, 0x6f, 0x56, 0x20, 0x93, 0x74, 0x88, 0xb6, 0xcd, 0x9b, 0x33, - 0xad, 0x62, 0x6f, 0xce, 0x54, 0x9d, 0xc9, 0xbb, 0x3d, 0x13, 0xb5, 0xa0, 0x12, 0x6e, 0x38, 0xb1, - 0x34, 0xab, 0x5f, 0x56, 0xfb, 0x38, 0xda, 0x78, 0x6f, 0x77, 0xe2, 0xc7, 0xfb, 0xf3, 0xba, 0xd2, - 0xb9, 0x3a, 0xc5, 0x8b, 0x8d, 0x68, 0xd6, 0x8c, 0x06, 0xe6, 0xf4, 0x0f, 0x72, 0x37, 0xdc, 0x27, - 0x44, 0x19, 0x78, 0x4c, 0xe2, 0x8e, 0x97, 0x88, 0xd9, 0xf0, 0x72, 0x81, 0xab, 0x8c, 0x13, 0xd6, - 0xe9, 0xf2, 0xfc, 0x3f, 0x36, 0x98, 0xa2, 0x0f, 0x42, 0x2d, 0x4e, 0x9c, 0x28, 0x39, 0x64, 0x82, - 0xab, 0x1a, 0xf4, 0x15, 0x49, 0x04, 0x6b, 0x7a, 0xe8, 0x15, 0x56, 0xdb, 0xd5, 0x8d, 0x37, 0x0e, - 0x99, 0xf3, 0x20, 0xeb, 0xc0, 0x0a, 0x0a, 0xd8, 0xa0, 0x86, 0x2e, 0x02, 0xb0, 0xb9, 0xcd, 0x03, - 0xfd, 0xaa, 0xcc, 0xcb, 0xa4, 0x44, 0x21, 0x56, 0x10, 0x6c, 0x60, 0xd9, 0x3f, 0x08, 0xe9, 0x7a, - 0x0f, 0x68, 0x42, 0x96, 0x97, 0xe0, 0x5e, 0x68, 0x96, 0xbb, 0x90, 0xaa, 0x04, 0xf1, 0xeb, 0x16, - 0x98, 0x45, 0x29, 0xd0, 0xeb, 0xbc, 0xfa, 0x85, 0x55, 0xc4, 0xc9, 0xa1, 0x41, 0x77, 0x72, 0xd1, - 0x09, 0x33, 0x47, 0xd8, 0xb2, 0x04, 0xc6, 0xf9, 0xf7, 0x40, 0x55, 0x42, 0x0f, 0x64, 0xd4, 0x7d, - 0x0c, 0x4e, 0x67, 0xef, 0x15, 0x17, 0xa7, 0x4e, 0xfb, 0xbb, 0x7e, 0xa4, 0x3f, 0xa7, 0xd4, 0xcb, - 0x9f, 0xd3, 0xc7, 0xfd, 0xa9, 0xbf, 0x61, 0xc1, 0x85, 0xfd, 0xae, 0x3f, 0x47, 0x8f, 0xc1, 0xc0, - 0x1d, 0x27, 0x92, 0x45, 0xb7, 0x99, 0xa0, 0xbc, 0xe5, 0x44, 0x3e, 0x66, 0xad, 0x68, 0x07, 0x06, - 0x79, 0x34, 0x98, 0xb0, 0xd6, 0x5f, 0x2e, 0xf6, 0x32, 0xf6, 0x6b, 0xc4, 0xd8, 0x2e, 0xf0, 0x48, - 0x34, 0x2c, 0x18, 0xda, 0xdf, 0xb6, 0x00, 0x2d, 0x6d, 0x91, 0x28, 0x72, 0x9b, 0x46, 0xfc, 0x1a, - 0xbb, 0x4e, 0xc5, 0xb8, 0x36, 0xc5, 0x4c, 0x71, 0xcd, 0x5c, 0xa7, 0x62, 0xfc, 0xcb, 0xbf, 0x4e, - 0xa5, 0x74, 0xb0, 0xeb, 0x54, 0xd0, 0x12, 0x9c, 0x6d, 0xf3, 0xed, 0x06, 0xbf, 0xa2, 0x80, 0xef, - 0x3d, 0x54, 0x42, 0xd9, 0xb9, 0xbb, 0xbb, 0x13, 0x67, 0x17, 0xf3, 0x10, 0x70, 0xfe, 0x73, 0xf6, - 0x7b, 0x00, 0xf1, 0xb0, 0xb5, 0x99, 0xbc, 0x18, 0xa4, 0x9e, 0xee, 0x17, 0xfb, 0xcb, 0x15, 0x38, - 0x91, 0x29, 0xc9, 0x4a, 0xb7, 0x7a, 0xdd, 0x41, 0x4f, 0x47, 0xd6, 0xdf, 0xdd, 0xdd, 0xeb, 0x2b, - 0x8c, 0xca, 0x87, 0x8a, 0xeb, 0x87, 0x9d, 0xa4, 0x98, 0x1c, 0x52, 0xde, 0x89, 0x79, 0x4a, 0xd0, - 0x70, 0x17, 0xd3, 0xbf, 0x98, 0xb3, 0x29, 0x32, 0x28, 0x2b, 0x65, 0x8c, 0x0f, 0x3c, 0x20, 0x77, - 0xc0, 0x27, 0x74, 0x88, 0x54, 0xa5, 0x08, 0xc7, 0x62, 0x66, 0xb2, 0x1c, 0xf7, 0x51, 0xfb, 0xaf, - 0x95, 0x60, 0xd8, 0xf8, 0x68, 0xe8, 0x17, 0xd3, 0x25, 0x9b, 0xac, 0xe2, 0x5e, 0x89, 0xd1, 0x9f, - 0xd4, 0x45, 0x99, 0xf8, 0x2b, 0x3d, 0xd5, 0x5d, 0xad, 0xe9, 0xde, 0xee, 0xc4, 0xc9, 0x4c, 0x3d, - 0xa6, 0x54, 0x05, 0xa7, 0xf3, 0x1f, 0x85, 0x13, 0x19, 0x32, 0x39, 0xaf, 0xbc, 0x9a, 0xbe, 0x36, - 0xfe, 0x88, 0x6e, 0x29, 0x73, 0xc8, 0xbe, 0x4e, 0x87, 0x4c, 0xa4, 0xd1, 0x05, 0x1e, 0xe9, 0xc3, - 0x07, 0x9b, 0xc9, 0x96, 0x2d, 0xf5, 0x99, 0x2d, 0xfb, 0x34, 0x54, 0xc3, 0xc0, 0x73, 0x1b, 0xae, - 0xaa, 0x42, 0xc8, 0xf2, 0x73, 0x97, 0x45, 0x1b, 0x56, 0x50, 0x74, 0x07, 0x6a, 0xea, 0x86, 0x7d, - 0xe1, 0xdf, 0x2e, 0xea, 0xd0, 0x47, 0x19, 0x2d, 0xfa, 0xe6, 0x7c, 0xcd, 0x0b, 0xd9, 0x30, 0xc8, - 0x94, 0xa0, 0x0c, 0xfd, 0x67, 0xbe, 0x77, 0xa6, 0x1d, 0x63, 0x2c, 0x20, 0xf6, 0xd7, 0x6a, 0x70, - 0x26, 0xaf, 0x2e, 0x36, 0xfa, 0x08, 0x0c, 0xf2, 0x3e, 0x16, 0x73, 0xf5, 0x42, 0x1e, 0x8f, 0x39, - 0x46, 0x50, 0x74, 0x8b, 0xfd, 0xc6, 0x82, 0xa7, 0xe0, 0xee, 0x39, 0x6b, 0x62, 0x86, 0x1c, 0x0f, - 0xf7, 0x05, 0x47, 0x73, 0x5f, 0x70, 0x38, 0x77, 0xcf, 0x59, 0x43, 0xdb, 0x50, 0x69, 0xb9, 0x09, - 0x71, 0x84, 0x13, 0xe1, 0xd6, 0xb1, 0x30, 0x27, 0x0e, 0xb7, 0xd2, 0xd8, 0x4f, 0xcc, 0x19, 0xa2, - 0xaf, 0x5a, 0x70, 0x62, 0x2d, 0x9d, 0x1a, 0x2f, 0x84, 0xa7, 0x73, 0x0c, 0xb5, 0xcf, 0xd3, 0x8c, - 0xf8, 0x7d, 0x42, 0x99, 0x46, 0x9c, 0xed, 0x0e, 0xfa, 0xa4, 0x05, 0x43, 0xeb, 0xae, 0x67, 0x94, - 0xc1, 0x3d, 0x86, 0x8f, 0x73, 0x99, 0x31, 0xd0, 0x3b, 0x0e, 0xfe, 0x3f, 0xc6, 0x92, 0x73, 0x2f, - 0x4d, 0x35, 0x78, 0x54, 0x4d, 0x35, 0xf4, 0x80, 0x34, 0xd5, 0xa7, 0x2d, 0xa8, 0xa9, 0x91, 0x16, - 0xe9, 0xce, 0x1f, 0x3c, 0xc6, 0x4f, 0xce, 0x3d, 0x27, 0xea, 0x2f, 0xd6, 0xcc, 0xd1, 0x17, 0x2c, - 0x18, 0x76, 0xde, 0xe8, 0x44, 0xa4, 0x49, 0xb6, 0x82, 0x30, 0x16, 0x97, 0x11, 0xbe, 0x5a, 0x7c, - 0x67, 0xa6, 0x29, 0x93, 0x59, 0xb2, 0xb5, 0x14, 0xc6, 0x22, 0x2d, 0x49, 0x37, 0x60, 0xb3, 0x0b, - 0xf6, 0x6e, 0x09, 0x26, 0xf6, 0xa1, 0x80, 0x5e, 0x84, 0x91, 0x20, 0x6a, 0x39, 0xbe, 0xfb, 0x86, - 0x59, 0xeb, 0x42, 0x59, 0x59, 0x4b, 0x06, 0x0c, 0xa7, 0x30, 0xcd, 0x84, 0xec, 0xd2, 0x3e, 0x09, - 0xd9, 0x17, 0x60, 0x20, 0x22, 0x61, 0x90, 0xdd, 0x2c, 0xb0, 0x94, 0x00, 0x06, 0x41, 0x8f, 0x43, - 0xd9, 0x09, 0x5d, 0x11, 0x88, 0xa6, 0xf6, 0x40, 0xd3, 0xcb, 0xf3, 0x98, 0xb6, 0xa7, 0xea, 0x43, - 0x54, 0xee, 0x4b, 0x7d, 0x08, 0xaa, 0x06, 0xc4, 0xd9, 0xc5, 0xa0, 0x56, 0x03, 0xe9, 0x33, 0x05, - 0xfb, 0xad, 0x32, 0x3c, 0xbe, 0xe7, 0x7c, 0xd1, 0x71, 0x78, 0xd6, 0x1e, 0x71, 0x78, 0x72, 0x78, - 0x4a, 0xfb, 0x0d, 0x4f, 0xb9, 0xc7, 0xf0, 0x7c, 0x92, 0x2e, 0x03, 0x59, 0x23, 0xa4, 0x98, 0xeb, - 0xe4, 0x7a, 0x95, 0x1c, 0x11, 0x2b, 0x40, 0x42, 0xb1, 0xe6, 0x4b, 0xf7, 0x00, 0xa9, 0x64, 0xe4, - 0x4a, 0x11, 0x6a, 0xa0, 0x67, 0xcd, 0x10, 0x3e, 0xf7, 0x7b, 0x65, 0x38, 0xdb, 0x3f, 0x5f, 0x82, - 0x27, 0xfb, 0x90, 0xde, 0xe6, 0x2c, 0xb6, 0xfa, 0x9c, 0xc5, 0xdf, 0xdd, 0x9f, 0xc9, 0xfe, 0x6b, - 0x16, 0x9c, 0xef, 0xad, 0x3c, 0xd0, 0x73, 0x30, 0xbc, 0x16, 0x39, 0x7e, 0x63, 0x83, 0x5d, 0x91, - 0x29, 0x07, 0x85, 0x8d, 0xb5, 0x6e, 0xc6, 0x26, 0x0e, 0xdd, 0xde, 0xf2, 0x98, 0x04, 0x03, 0x43, - 0x26, 0x8f, 0xd2, 0xed, 0xed, 0x6a, 0x16, 0x88, 0xbb, 0xf1, 0xed, 0x3f, 0x2b, 0xe5, 0x77, 0x8b, - 0x1b, 0x19, 0x07, 0xf9, 0x4e, 0xe2, 0x2b, 0x94, 0xfa, 0x90, 0x25, 0xe5, 0xfb, 0x2d, 0x4b, 0x06, - 0x7a, 0xc9, 0x12, 0x34, 0x0b, 0x27, 0x8d, 0x2b, 0x54, 0x78, 0x42, 0x30, 0x0f, 0xb8, 0x55, 0x55, - 0x32, 0x96, 0x33, 0x70, 0xdc, 0xf5, 0x04, 0x7a, 0x06, 0xaa, 0xae, 0x1f, 0x93, 0x46, 0x27, 0xe2, - 0x81, 0xde, 0x46, 0x12, 0xd6, 0xbc, 0x68, 0xc7, 0x0a, 0xc3, 0xfe, 0xa5, 0x12, 0x9c, 0xeb, 0x69, - 0x67, 0xdd, 0x27, 0xd9, 0x65, 0x7e, 0x8e, 0x81, 0xfb, 0xf3, 0x39, 0xcc, 0x41, 0xaa, 0xec, 0x3b, - 0x48, 0x7f, 0xd8, 0x7b, 0x62, 0x52, 0x9b, 0xfb, 0x7b, 0x76, 0x94, 0x5e, 0x82, 0x51, 0x27, 0x0c, - 0x39, 0x1e, 0x8b, 0xd7, 0xcc, 0x54, 0xc9, 0x99, 0x36, 0x81, 0x38, 0x8d, 0xdb, 0x97, 0xf6, 0xfc, - 0x63, 0x0b, 0x6a, 0x98, 0xac, 0x73, 0xe9, 0x80, 0x6e, 0x8b, 0x21, 0xb2, 0x8a, 0xa8, 0xa7, 0x49, - 0x07, 0x36, 0x76, 0x59, 0x9d, 0xc9, 0xbc, 0xc1, 0xee, 0xbe, 0x6a, 0xa7, 0x74, 0xa0, 0xab, 0x76, - 0xd4, 0x65, 0x2b, 0xe5, 0xde, 0x97, 0xad, 0xd8, 0x5f, 0x1f, 0xa2, 0xaf, 0x17, 0x06, 0x33, 0x11, - 0x69, 0xc6, 0xf4, 0xfb, 0x76, 0x22, 0x4f, 0x4c, 0x12, 0xf5, 0x7d, 0x6f, 0xe0, 0x05, 0x4c, 0xdb, - 0x53, 0x47, 0x31, 0xa5, 0x03, 0xd5, 0x08, 0x29, 0xef, 0x5b, 0x23, 0xe4, 0x25, 0x18, 0x8d, 0xe3, - 0x8d, 0xe5, 0xc8, 0xdd, 0x72, 0x12, 0x72, 0x8d, 0xec, 0x08, 0x2b, 0x4b, 0xe7, 0xf5, 0xaf, 0x5c, - 0xd1, 0x40, 0x9c, 0xc6, 0x45, 0x73, 0x70, 0x4a, 0x57, 0xea, 0x20, 0x51, 0xc2, 0xa2, 0xfb, 0xf9, - 0x4c, 0x50, 0x49, 0xbc, 0xba, 0xb6, 0x87, 0x40, 0xc0, 0xdd, 0xcf, 0x50, 0xf9, 0x96, 0x6a, 0xa4, - 0x1d, 0x19, 0x4c, 0xcb, 0xb7, 0x14, 0x1d, 0xda, 0x97, 0xae, 0x27, 0xd0, 0x22, 0x9c, 0xe6, 0x13, - 0x63, 0x3a, 0x0c, 0x8d, 0x37, 0x1a, 0x4a, 0xd7, 0x31, 0x9c, 0xeb, 0x46, 0xc1, 0x79, 0xcf, 0xa1, - 0x17, 0x60, 0x58, 0x35, 0xcf, 0xcf, 0x8a, 0x53, 0x04, 0xe5, 0xc5, 0x50, 0x64, 0xe6, 0x9b, 0xd8, - 0xc4, 0x43, 0x1f, 0x80, 0x47, 0xf5, 0x5f, 0x9e, 0x02, 0xc6, 0x8f, 0xd6, 0x66, 0x45, 0x11, 0x24, - 0x75, 0xb5, 0xc7, 0x5c, 0x2e, 0x5a, 0x13, 0xf7, 0x7a, 0x1e, 0xad, 0xc1, 0x79, 0x05, 0xba, 0xe4, - 0x27, 0x2c, 0x9f, 0x23, 0x26, 0x75, 0x27, 0x26, 0x37, 0x22, 0x4f, 0xdc, 0x8d, 0xaa, 0x6e, 0x5d, - 0x9c, 0x73, 0x93, 0x2b, 0x79, 0x98, 0x78, 0x01, 0xef, 0x41, 0x05, 0x4d, 0x41, 0x8d, 0xf8, 0xce, - 0x9a, 0x47, 0x96, 0x66, 0xe6, 0x59, 0x31, 0x25, 0xe3, 0x24, 0xef, 0x92, 0x04, 0x60, 0x8d, 0xa3, - 0x22, 0x4c, 0x47, 0x7a, 0xde, 0x00, 0xba, 0x0c, 0x67, 0x5a, 0x8d, 0x90, 0xda, 0x1e, 0x6e, 0x83, - 0x4c, 0x37, 0x58, 0x40, 0x1d, 0xfd, 0x30, 0xbc, 0xc0, 0xa4, 0x0a, 0x9f, 0x9e, 0x9b, 0x59, 0xee, - 0xc2, 0xc1, 0xb9, 0x4f, 0xb2, 0xc0, 0xcb, 0x28, 0xd8, 0xde, 0x19, 0x3f, 0x9d, 0x09, 0xbc, 0xa4, - 0x8d, 0x98, 0xc3, 0xd0, 0x55, 0x40, 0x2c, 0x16, 0xff, 0x4a, 0x92, 0x84, 0xca, 0xd8, 0x19, 0x3f, - 0xc3, 0x5e, 0x49, 0x85, 0x91, 0x5d, 0xee, 0xc2, 0xc0, 0x39, 0x4f, 0xd9, 0xff, 0xc1, 0x82, 0x51, - 0xb5, 0x5e, 0xef, 0x43, 0x36, 0x8a, 0x97, 0xce, 0x46, 0x99, 0x3b, 0xba, 0xc4, 0x63, 0x3d, 0xef, - 0x11, 0xd2, 0xfc, 0x33, 0xc3, 0x00, 0x5a, 0x2a, 0x2a, 0x85, 0x64, 0xf5, 0x54, 0x48, 0x0f, 0xad, - 0x44, 0xca, 0xab, 0x9c, 0x52, 0x79, 0xb0, 0x95, 0x53, 0x56, 0xe0, 0xac, 0x34, 0x17, 0xf8, 0x59, - 0xd1, 0x95, 0x20, 0x56, 0x02, 0xae, 0x5a, 0x7f, 0x5c, 0x10, 0x3a, 0x3b, 0x9f, 0x87, 0x84, 0xf3, - 0x9f, 0x4d, 0x59, 0x29, 0x43, 0xfb, 0x59, 0x29, 0x7a, 0x4d, 0x2f, 0xac, 0xcb, 0x3b, 0x3c, 0x32, - 0x6b, 0x7a, 0xe1, 0xf2, 0x0a, 0xd6, 0x38, 0xf9, 0x82, 0xbd, 0x56, 0x90, 0x60, 0x87, 0x03, 0x0b, - 0x76, 0x29, 0x62, 0x86, 0x7b, 0x8a, 0x18, 0xe9, 0x93, 0x1e, 0xe9, 0xe9, 0x93, 0x7e, 0x2f, 0x8c, - 0xb9, 0xfe, 0x06, 0x89, 0xdc, 0x84, 0x34, 0xd9, 0x5a, 0x60, 0xe2, 0xa7, 0xaa, 0xd5, 0xfa, 0x7c, - 0x0a, 0x8a, 0x33, 0xd8, 0x69, 0xb9, 0x38, 0xd6, 0x87, 0x5c, 0xec, 0xa1, 0x8d, 0x4e, 0x14, 0xa3, - 0x8d, 0x4e, 0x1e, 0x5d, 0x1b, 0x9d, 0x3a, 0x56, 0x6d, 0x84, 0x0a, 0xd1, 0x46, 0x7d, 0x09, 0x7a, - 0x63, 0xfb, 0x77, 0x66, 0x9f, 0xed, 0x5f, 0x2f, 0x55, 0x74, 0xf6, 0xd0, 0xaa, 0x28, 0x5f, 0xcb, - 0x3c, 0x72, 0x28, 0x2d, 0xf3, 0xe9, 0x12, 0x9c, 0xd5, 0x72, 0x98, 0xce, 0x7e, 0x77, 0x9d, 0x4a, - 0x22, 0x76, 0x0d, 0x14, 0x3f, 0xb7, 0x31, 0x92, 0xa3, 0x74, 0x9e, 0x95, 0x82, 0x60, 0x03, 0x8b, - 0xe5, 0x18, 0x91, 0x88, 0x95, 0xd1, 0xcd, 0x0a, 0xe9, 0x19, 0xd1, 0x8e, 0x15, 0x06, 0x9d, 0x5f, - 0xf4, 0xb7, 0xc8, 0xdb, 0xcc, 0x16, 0x8b, 0x9b, 0xd1, 0x20, 0x6c, 0xe2, 0xa1, 0xa7, 0x39, 0x13, - 0x26, 0x20, 0xa8, 0xa0, 0x1e, 0x11, 0xf7, 0xc2, 0x4a, 0x99, 0xa0, 0xa0, 0xb2, 0x3b, 0x2c, 0x99, - 0xac, 0xd2, 0xdd, 0x1d, 0x16, 0x02, 0xa5, 0x30, 0xec, 0xff, 0x69, 0xc1, 0xb9, 0xdc, 0xa1, 0xb8, - 0x0f, 0xca, 0x77, 0x3b, 0xad, 0x7c, 0x57, 0x8a, 0xda, 0x6e, 0x18, 0x6f, 0xd1, 0x43, 0x11, 0xff, - 0x3b, 0x0b, 0xc6, 0x34, 0xfe, 0x7d, 0x78, 0x55, 0x37, 0xfd, 0xaa, 0xc5, 0xed, 0xac, 0x6a, 0x5d, - 0xef, 0xf6, 0x3b, 0x25, 0x50, 0x05, 0x1c, 0xa7, 0x1b, 0xb2, 0x3c, 0xee, 0x3e, 0x27, 0x89, 0x3b, - 0x30, 0xc8, 0x0e, 0x42, 0xe3, 0x62, 0x82, 0x3c, 0xd2, 0xfc, 0xd9, 0xa1, 0xaa, 0x3e, 0x64, 0x66, - 0x7f, 0x63, 0x2c, 0x18, 0xb2, 0x22, 0xcf, 0x6e, 0x4c, 0xa5, 0x79, 0x53, 0xa4, 0x65, 0xe9, 0x22, - 0xcf, 0xa2, 0x1d, 0x2b, 0x0c, 0xaa, 0x1e, 0xdc, 0x46, 0xe0, 0xcf, 0x78, 0x4e, 0x2c, 0xef, 0x3e, - 0x54, 0xea, 0x61, 0x5e, 0x02, 0xb0, 0xc6, 0x61, 0x67, 0xa4, 0x6e, 0x1c, 0x7a, 0xce, 0x8e, 0xb1, - 0x7f, 0x36, 0xea, 0x13, 0x28, 0x10, 0x36, 0xf1, 0xec, 0x36, 0x8c, 0xa7, 0x5f, 0x62, 0x96, 0xac, - 0xb3, 0x00, 0xc5, 0xbe, 0x86, 0x73, 0x0a, 0x6a, 0x0e, 0x7b, 0x6a, 0xa1, 0xe3, 0x64, 0xaf, 0x2c, - 0x9f, 0x96, 0x00, 0xac, 0x71, 0xec, 0x5f, 0xb5, 0xe0, 0x74, 0xce, 0xa0, 0x15, 0x98, 0xf6, 0x96, - 0x68, 0x69, 0x93, 0xa7, 0xd8, 0x7f, 0x00, 0x86, 0x9a, 0x64, 0xdd, 0x91, 0x21, 0x70, 0x86, 0x6c, - 0x9f, 0xe5, 0xcd, 0x58, 0xc2, 0xed, 0xff, 0x6e, 0xc1, 0x89, 0x74, 0x5f, 0x63, 0x96, 0x4a, 0xc2, - 0x87, 0xc9, 0x8d, 0x1b, 0xc1, 0x16, 0x89, 0x76, 0xe8, 0x9b, 0x5b, 0x99, 0x54, 0x92, 0x2e, 0x0c, - 0x9c, 0xf3, 0x14, 0x2b, 0xdf, 0xda, 0x54, 0xa3, 0x2d, 0x67, 0xe4, 0xcd, 0x22, 0x67, 0xa4, 0xfe, - 0x98, 0xe6, 0x71, 0xb9, 0x62, 0x89, 0x4d, 0xfe, 0xf6, 0xb7, 0x07, 0x40, 0xe5, 0xc5, 0xb2, 0xf8, - 0xa3, 0x82, 0xa2, 0xb7, 0x0e, 0x9a, 0x41, 0xa4, 0x26, 0xc3, 0xc0, 0x5e, 0x01, 0x01, 0xdc, 0x4b, - 0x62, 0xba, 0x2e, 0xd5, 0x1b, 0xae, 0x6a, 0x10, 0x36, 0xf1, 0x68, 0x4f, 0x3c, 0x77, 0x8b, 0xf0, - 0x87, 0x06, 0xd3, 0x3d, 0x59, 0x90, 0x00, 0xac, 0x71, 0x68, 0x4f, 0x9a, 0xee, 0xfa, 0xba, 0xd8, - 0xf2, 0xab, 0x9e, 0xd0, 0xd1, 0xc1, 0x0c, 0xc2, 0x2b, 0x72, 0x07, 0x9b, 0xc2, 0x0a, 0x36, 0x2a, - 0x72, 0x07, 0x9b, 0x98, 0x41, 0xa8, 0xdd, 0xe6, 0x07, 0x51, 0x9b, 0x5d, 0x29, 0xdf, 0x54, 0x5c, - 0x84, 0xf5, 0xab, 0xec, 0xb6, 0xeb, 0xdd, 0x28, 0x38, 0xef, 0x39, 0x3a, 0x03, 0xc3, 0x88, 0x34, - 0xdd, 0x46, 0x62, 0x52, 0x83, 0xf4, 0x0c, 0x5c, 0xee, 0xc2, 0xc0, 0x39, 0x4f, 0xa1, 0x69, 0x38, - 0x21, 0xf3, 0x9a, 0x65, 0xd5, 0x9a, 0xe1, 0x74, 0x95, 0x0c, 0x9c, 0x06, 0xe3, 0x2c, 0x3e, 0x95, - 0x6a, 0x6d, 0x51, 0xb0, 0x8a, 0x19, 0xcb, 0x86, 0x54, 0x93, 0x85, 0xac, 0xb0, 0xc2, 0xb0, 0x3f, - 0x51, 0xa6, 0x5a, 0xb8, 0x47, 0xa1, 0xb6, 0xfb, 0x16, 0x2d, 0x98, 0x9e, 0x91, 0x03, 0x7d, 0xcc, - 0xc8, 0xe7, 0x61, 0xe4, 0x76, 0x1c, 0xf8, 0x2a, 0x12, 0xaf, 0xd2, 0x33, 0x12, 0xcf, 0xc0, 0xca, - 0x8f, 0xc4, 0x1b, 0x2c, 0x2a, 0x12, 0x6f, 0xe8, 0x90, 0x91, 0x78, 0xdf, 0xac, 0x80, 0xba, 0x1a, - 0xe4, 0x3a, 0x49, 0xee, 0x04, 0xd1, 0xa6, 0xeb, 0xb7, 0x58, 0x3e, 0xf8, 0x57, 0x2d, 0x18, 0xe1, - 0xeb, 0x65, 0xc1, 0xcc, 0xa4, 0x5a, 0x2f, 0xe8, 0xce, 0x89, 0x14, 0xb3, 0xc9, 0x55, 0x83, 0x51, - 0xe6, 0xea, 0x4d, 0x13, 0x84, 0x53, 0x3d, 0x42, 0x1f, 0x05, 0x90, 0xfe, 0xd1, 0x75, 0x29, 0x32, - 0xe7, 0x8b, 0xe9, 0x1f, 0x26, 0xeb, 0xda, 0x06, 0x5e, 0x55, 0x4c, 0xb0, 0xc1, 0x10, 0x7d, 0x5a, - 0x67, 0x99, 0xf1, 0x90, 0xfd, 0x0f, 0x1f, 0xcb, 0xd8, 0xf4, 0x93, 0x63, 0x86, 0x61, 0xc8, 0xf5, - 0x5b, 0x74, 0x9e, 0x88, 0x88, 0xa5, 0x77, 0xe5, 0xd5, 0x52, 0x58, 0x08, 0x9c, 0x66, 0xdd, 0xf1, - 0x1c, 0xbf, 0x41, 0xa2, 0x79, 0x8e, 0x6e, 0x5e, 0x38, 0xcd, 0x1a, 0xb0, 0x24, 0xd4, 0x75, 0xa9, - 0x4a, 0xa5, 0x9f, 0x4b, 0x55, 0xce, 0xbf, 0x0f, 0x4e, 0x75, 0x7d, 0xcc, 0x03, 0xa5, 0x94, 0x1d, - 0x3e, 0x1b, 0xcd, 0xfe, 0x17, 0x83, 0x5a, 0x69, 0x5d, 0x0f, 0x9a, 0xfc, 0x6a, 0x8f, 0x48, 0x7f, - 0x51, 0x61, 0xe3, 0x16, 0x38, 0x45, 0x8c, 0x4b, 0xab, 0x55, 0x23, 0x36, 0x59, 0xd2, 0x39, 0x1a, - 0x3a, 0x11, 0xf1, 0x8f, 0x7b, 0x8e, 0x2e, 0x2b, 0x26, 0xd8, 0x60, 0x88, 0x36, 0x52, 0x39, 0x25, - 0x97, 0x8f, 0x9e, 0x53, 0xc2, 0xaa, 0x4c, 0xe5, 0x55, 0xe3, 0xff, 0x82, 0x05, 0x63, 0x7e, 0x6a, - 0xe6, 0x16, 0x13, 0x46, 0x9a, 0xbf, 0x2a, 0xf8, 0xcd, 0x52, 0xe9, 0x36, 0x9c, 0xe1, 0x9f, 0xa7, - 0xd2, 0x2a, 0x07, 0x54, 0x69, 0xfa, 0x8e, 0xa0, 0xc1, 0x5e, 0x77, 0x04, 0x21, 0x5f, 0x5d, 0x92, - 0x36, 0x54, 0xf8, 0x25, 0x69, 0x90, 0x73, 0x41, 0xda, 0x2d, 0xa8, 0x35, 0x22, 0xe2, 0x24, 0x87, - 0xbc, 0x2f, 0x8b, 0x1d, 0xd0, 0xcf, 0x48, 0x02, 0x58, 0xd3, 0xb2, 0xff, 0xcf, 0x00, 0x9c, 0x94, - 0x23, 0x22, 0x43, 0xd0, 0xa9, 0x7e, 0xe4, 0x7c, 0xb5, 0x71, 0xab, 0xf4, 0xe3, 0x15, 0x09, 0xc0, - 0x1a, 0x87, 0xda, 0x63, 0x9d, 0x98, 0x2c, 0x85, 0xc4, 0x5f, 0x70, 0xd7, 0x62, 0x71, 0xce, 0xa9, - 0x16, 0xca, 0x0d, 0x0d, 0xc2, 0x26, 0x1e, 0x35, 0xc6, 0xb9, 0x5d, 0x1c, 0x67, 0xd3, 0x57, 0x84, - 0xbd, 0x8d, 0x25, 0x1c, 0xfd, 0x42, 0x6e, 0xe5, 0xd8, 0x62, 0x12, 0xb7, 0xba, 0x22, 0xef, 0x0f, - 0x78, 0xc5, 0xe2, 0xdf, 0xb1, 0xe0, 0x2c, 0x6f, 0x95, 0x23, 0x79, 0x23, 0x6c, 0x3a, 0x09, 0x89, - 0x8b, 0xa9, 0xe4, 0x9e, 0xd3, 0x3f, 0xed, 0xe4, 0xcd, 0x63, 0x8b, 0xf3, 0x7b, 0x83, 0xde, 0xb4, - 0xe0, 0xc4, 0x66, 0xaa, 0xe6, 0x87, 0x54, 0x1d, 0x47, 0x4d, 0xc7, 0x4f, 0x11, 0xd5, 0x4b, 0x2d, - 0xdd, 0x1e, 0xe3, 0x2c, 0x77, 0xfb, 0xcf, 0x2c, 0x30, 0xc5, 0xe8, 0xfd, 0x2f, 0x15, 0x72, 0x70, - 0x53, 0x50, 0x5a, 0x97, 0x95, 0x9e, 0xd6, 0xe5, 0xe3, 0x50, 0xee, 0xb8, 0x4d, 0xb1, 0xbf, 0xd0, - 0xa7, 0xaf, 0xf3, 0xb3, 0x98, 0xb6, 0xdb, 0xff, 0xb4, 0xa2, 0xfd, 0x16, 0x22, 0x2f, 0xea, 0x7b, - 0xe2, 0xb5, 0xd7, 0x55, 0xb1, 0x31, 0xfe, 0xe6, 0xd7, 0xbb, 0x8a, 0x8d, 0xfd, 0xe8, 0xc1, 0xd3, - 0xde, 0xf8, 0x00, 0xf5, 0xaa, 0x35, 0x36, 0xb4, 0x4f, 0xce, 0xdb, 0x6d, 0xa8, 0xd2, 0x2d, 0x18, - 0x73, 0x40, 0x56, 0x53, 0x9d, 0xaa, 0x5e, 0x11, 0xed, 0xf7, 0x76, 0x27, 0x7e, 0xe4, 0xe0, 0xdd, - 0x92, 0x4f, 0x63, 0x45, 0x1f, 0xc5, 0x50, 0xa3, 0xbf, 0x59, 0x7a, 0x9e, 0xd8, 0xdc, 0xdd, 0x50, - 0x32, 0x53, 0x02, 0x0a, 0xc9, 0xfd, 0xd3, 0x7c, 0x90, 0x0f, 0x35, 0x76, 0x1b, 0x2d, 0x63, 0xca, - 0xf7, 0x80, 0xcb, 0x2a, 0x49, 0x4e, 0x02, 0xee, 0xed, 0x4e, 0xbc, 0x74, 0x70, 0xa6, 0xea, 0x71, - 0xac, 0x59, 0xd8, 0x5f, 0x1c, 0xd0, 0x73, 0x57, 0xd4, 0x98, 0xfb, 0x9e, 0x98, 0xbb, 0x2f, 0x66, - 0xe6, 0xee, 0x85, 0xae, 0xb9, 0x3b, 0xa6, 0x6f, 0x4d, 0x4d, 0xcd, 0xc6, 0xfb, 0x6d, 0x08, 0xec, - 0xef, 0x6f, 0x60, 0x16, 0xd0, 0xeb, 0x1d, 0x37, 0x22, 0xf1, 0x72, 0xd4, 0xf1, 0x5d, 0xbf, 0xc5, - 0xa6, 0x63, 0xd5, 0xb4, 0x80, 0x52, 0x60, 0x9c, 0xc5, 0xa7, 0x9b, 0x7a, 0xfa, 0xcd, 0x6f, 0x39, - 0x5b, 0x7c, 0x56, 0x19, 0x65, 0xb7, 0x56, 0x44, 0x3b, 0x56, 0x18, 0xf6, 0xd7, 0xd9, 0x59, 0xb6, - 0x91, 0x17, 0x4c, 0xe7, 0x84, 0xc7, 0xae, 0xff, 0xe5, 0x35, 0xbb, 0xd4, 0x9c, 0xe0, 0x77, 0xfe, - 0x72, 0x18, 0xba, 0x03, 0x43, 0x6b, 0xfc, 0xfe, 0xbb, 0x62, 0xea, 0x93, 0x8b, 0xcb, 0xf4, 0xd8, - 0x2d, 0x27, 0xf2, 0x66, 0xbd, 0x7b, 0xfa, 0x27, 0x96, 0xdc, 0xec, 0xdf, 0xaf, 0xc0, 0x89, 0xcc, - 0x05, 0xb1, 0xa9, 0x6a, 0xa9, 0xa5, 0x7d, 0xab, 0xa5, 0x7e, 0x08, 0xa0, 0x49, 0x42, 0x2f, 0xd8, - 0x61, 0xe6, 0xd8, 0xc0, 0x81, 0xcd, 0x31, 0x65, 0xc1, 0xcf, 0x2a, 0x2a, 0xd8, 0xa0, 0x28, 0x0a, - 0x95, 0xf1, 0xe2, 0xab, 0x99, 0x42, 0x65, 0xc6, 0x2d, 0x06, 0x83, 0xf7, 0xf7, 0x16, 0x03, 0x17, - 0x4e, 0xf0, 0x2e, 0xaa, 0xec, 0xdb, 0x43, 0x24, 0xd9, 0xb2, 0xfc, 0x85, 0xd9, 0x34, 0x19, 0x9c, - 0xa5, 0xfb, 0x20, 0xef, 0x7f, 0x46, 0xef, 0x86, 0x9a, 0xfc, 0xce, 0xf1, 0x78, 0x4d, 0x57, 0x30, - 0x90, 0xd3, 0x80, 0xdd, 0xcb, 0x2c, 0x7e, 0x76, 0x15, 0x12, 0x80, 0x07, 0x55, 0x48, 0xc0, 0xfe, - 0x7c, 0x89, 0xda, 0xf1, 0xbc, 0x5f, 0xaa, 0x26, 0xce, 0x53, 0x30, 0xe8, 0x74, 0x92, 0x8d, 0xa0, - 0xeb, 0x36, 0xbf, 0x69, 0xd6, 0x8a, 0x05, 0x14, 0x2d, 0xc0, 0x40, 0x53, 0xd7, 0x39, 0x39, 0xc8, - 0xf7, 0xd4, 0x2e, 0x51, 0x27, 0x21, 0x98, 0x51, 0x41, 0x8f, 0xc1, 0x40, 0xe2, 0xb4, 0x64, 0xca, - 0x15, 0x4b, 0xb3, 0x5d, 0x75, 0x5a, 0x31, 0x66, 0xad, 0xa6, 0xfa, 0x1e, 0xd8, 0x47, 0x7d, 0xbf, - 0x04, 0xa3, 0xb1, 0xdb, 0xf2, 0x9d, 0xa4, 0x13, 0x11, 0xe3, 0x98, 0x4f, 0x47, 0x6e, 0x98, 0x40, - 0x9c, 0xc6, 0xb5, 0x7f, 0x73, 0x04, 0xce, 0xac, 0xcc, 0x2c, 0xca, 0xea, 0xdd, 0xc7, 0x96, 0x35, - 0x95, 0xc7, 0xe3, 0xfe, 0x65, 0x4d, 0xf5, 0xe0, 0xee, 0x19, 0x59, 0x53, 0x9e, 0x91, 0x35, 0x95, - 0x4e, 0x61, 0x29, 0x17, 0x91, 0xc2, 0x92, 0xd7, 0x83, 0x7e, 0x52, 0x58, 0x8e, 0x2d, 0x8d, 0x6a, - 0xcf, 0x0e, 0x1d, 0x28, 0x8d, 0x4a, 0xe5, 0x98, 0x15, 0x92, 0x5c, 0xd0, 0xe3, 0x53, 0xe5, 0xe6, - 0x98, 0xa9, 0xfc, 0x1e, 0x9e, 0x38, 0x23, 0x44, 0xfd, 0xab, 0xc5, 0x77, 0xa0, 0x8f, 0xfc, 0x1e, - 0x91, 0xbb, 0x63, 0xe6, 0x94, 0x0d, 0x15, 0x91, 0x53, 0x96, 0xd7, 0x9d, 0x7d, 0x73, 0xca, 0x5e, - 0x82, 0xd1, 0x86, 0x17, 0xf8, 0x64, 0x39, 0x0a, 0x92, 0xa0, 0x11, 0x78, 0xc2, 0xac, 0x57, 0x22, - 0x61, 0xc6, 0x04, 0xe2, 0x34, 0x6e, 0xaf, 0x84, 0xb4, 0xda, 0x51, 0x13, 0xd2, 0xe0, 0x01, 0x25, - 0xa4, 0xfd, 0xac, 0x4e, 0x9d, 0x1e, 0x66, 0x5f, 0xe4, 0x43, 0xc5, 0x7f, 0x91, 0x7e, 0xf2, 0xa7, - 0xd1, 0x5b, 0xfc, 0x3a, 0x3d, 0x6a, 0x18, 0xcf, 0x04, 0x6d, 0x6a, 0xf8, 0x8d, 0xb0, 0x21, 0x79, - 0xed, 0x18, 0x26, 0xec, 0xad, 0x15, 0xcd, 0x46, 0x5d, 0xb1, 0xa7, 0x9b, 0x70, 0xba, 0x23, 0x47, - 0x49, 0xed, 0xfe, 0x72, 0x09, 0xbe, 0x6f, 0xdf, 0x2e, 0xa0, 0x3b, 0x00, 0x89, 0xd3, 0x12, 0x13, - 0x55, 0x1c, 0x98, 0x1c, 0x31, 0xbc, 0x72, 0x55, 0xd2, 0xe3, 0x35, 0x49, 0xd4, 0x5f, 0x76, 0x14, - 0x21, 0x7f, 0xb3, 0xa8, 0xca, 0xc0, 0xeb, 0x2a, 0xdd, 0x88, 0x03, 0x8f, 0x60, 0x06, 0xa1, 0xea, - 0x3f, 0x22, 0x2d, 0x7d, 0xff, 0xb3, 0xfa, 0x7c, 0x98, 0xb5, 0x62, 0x01, 0x45, 0x2f, 0xc0, 0xb0, - 0xe3, 0x79, 0x3c, 0x3f, 0x86, 0xc4, 0xe2, 0x3e, 0x1d, 0x5d, 0x43, 0x4e, 0x83, 0xb0, 0x89, 0x67, - 0xff, 0x69, 0x09, 0x26, 0xf6, 0x91, 0x29, 0x5d, 0x19, 0x7f, 0x95, 0xbe, 0x33, 0xfe, 0x44, 0x8e, - 0xc2, 0x60, 0x8f, 0x1c, 0x85, 0x17, 0x60, 0x38, 0x21, 0x4e, 0x5b, 0x04, 0x64, 0x09, 0x4f, 0x80, - 0x3e, 0x01, 0xd6, 0x20, 0x6c, 0xe2, 0x51, 0x29, 0x36, 0xe6, 0x34, 0x1a, 0x24, 0x8e, 0x65, 0x12, - 0x82, 0xf0, 0xa6, 0x16, 0x96, 0xe1, 0xc0, 0x9c, 0xd4, 0xd3, 0x29, 0x16, 0x38, 0xc3, 0x32, 0x3b, - 0xe0, 0xb5, 0x3e, 0x07, 0xfc, 0x6b, 0x25, 0x78, 0x7c, 0x4f, 0xed, 0xd6, 0x77, 0x7e, 0x48, 0x27, - 0x26, 0x51, 0x76, 0xe2, 0xdc, 0x88, 0x49, 0x84, 0x19, 0x84, 0x8f, 0x52, 0x18, 0x1a, 0xf7, 0x6b, - 0x17, 0x9d, 0xbc, 0xc4, 0x47, 0x29, 0xc5, 0x02, 0x67, 0x58, 0x1e, 0x76, 0x5a, 0xfe, 0xfd, 0x12, - 0x3c, 0xd9, 0x87, 0x0d, 0x50, 0x60, 0x92, 0x57, 0x3a, 0xd5, 0xae, 0xfc, 0x80, 0x32, 0x22, 0x0f, - 0x39, 0x5c, 0x5f, 0x2f, 0xc1, 0xf9, 0xde, 0xaa, 0x18, 0xfd, 0x18, 0x9c, 0x88, 0x54, 0x14, 0x96, - 0x99, 0xa5, 0x77, 0x9a, 0x7b, 0x12, 0x52, 0x20, 0x9c, 0xc5, 0x45, 0x93, 0x00, 0xa1, 0x93, 0x6c, - 0xc4, 0x97, 0xb6, 0xdd, 0x38, 0x11, 0x55, 0x68, 0xc6, 0xf8, 0xd9, 0x95, 0x6c, 0xc5, 0x06, 0x06, - 0x65, 0xc7, 0xfe, 0xcd, 0x06, 0xd7, 0x83, 0x84, 0x3f, 0xc4, 0xb7, 0x11, 0xa7, 0xe5, 0x9d, 0x1d, - 0x06, 0x08, 0x67, 0x71, 0x29, 0x3b, 0x76, 0x3a, 0xca, 0x3b, 0xca, 0xf7, 0x17, 0x8c, 0xdd, 0x82, - 0x6a, 0xc5, 0x06, 0x46, 0x36, 0xff, 0xb0, 0xb2, 0x7f, 0xfe, 0xa1, 0xfd, 0x4f, 0x4a, 0x70, 0xae, - 0xa7, 0x29, 0xd7, 0xdf, 0x02, 0x7c, 0xf8, 0x72, 0x06, 0x0f, 0x37, 0x77, 0x0e, 0x98, 0xdb, 0xf6, - 0xc7, 0x3d, 0x66, 0x9a, 0xc8, 0x6d, 0x3b, 0x7c, 0x72, 0xf8, 0xc3, 0x37, 0x9e, 0x5d, 0xe9, 0x6c, - 0x03, 0x07, 0x48, 0x67, 0xcb, 0x7c, 0x8c, 0x4a, 0x9f, 0x0b, 0xf9, 0xcf, 0xcb, 0x3d, 0x87, 0x97, - 0x6e, 0xfd, 0xfa, 0xf2, 0xd3, 0xce, 0xc2, 0x49, 0xd7, 0x67, 0xf7, 0x37, 0xad, 0x74, 0xd6, 0x44, - 0x61, 0x92, 0x52, 0xfa, 0xf6, 0xf4, 0xf9, 0x0c, 0x1c, 0x77, 0x3d, 0xf1, 0x10, 0xa6, 0x17, 0x1e, - 0x6e, 0x48, 0x0f, 0x96, 0xe0, 0x8a, 0x96, 0xe0, 0xac, 0x1c, 0x8a, 0x0d, 0x27, 0x22, 0x4d, 0xa1, - 0x46, 0x62, 0x91, 0x50, 0x71, 0x8e, 0x27, 0x65, 0xe4, 0x20, 0xe0, 0xfc, 0xe7, 0xd8, 0x95, 0x39, - 0x41, 0xe8, 0x36, 0xc4, 0x26, 0x47, 0x5f, 0x99, 0x43, 0x1b, 0x31, 0x87, 0xd9, 0x1f, 0x82, 0x9a, - 0x7a, 0x7f, 0x1e, 0xd6, 0xad, 0x26, 0x5d, 0x57, 0x58, 0xb7, 0x9a, 0x71, 0x06, 0x16, 0xfd, 0x5a, - 0xd4, 0x24, 0xce, 0xac, 0x9e, 0x6b, 0x64, 0x87, 0xd9, 0xc7, 0xf6, 0x0f, 0xc1, 0x88, 0xf2, 0xb3, - 0xf4, 0x7b, 0x91, 0x90, 0xfd, 0xc5, 0x41, 0x18, 0x4d, 0x15, 0x07, 0x4c, 0x39, 0x58, 0xad, 0x7d, - 0x1d, 0xac, 0x2c, 0x4c, 0xbf, 0xe3, 0xcb, 0x5b, 0xc6, 0x8c, 0x30, 0xfd, 0x8e, 0x4f, 0x30, 0x87, - 0x51, 0xf3, 0xb6, 0x19, 0xed, 0xe0, 0x8e, 0x2f, 0xc2, 0x69, 0x95, 0x79, 0x3b, 0xcb, 0x5a, 0xb1, - 0x80, 0xa2, 0x8f, 0x5b, 0x30, 0x12, 0x33, 0xef, 0x3d, 0x77, 0x4f, 0x8b, 0x49, 0x77, 0xf5, 0xe8, - 0xb5, 0x0f, 0x55, 0x21, 0x4c, 0x16, 0x21, 0x63, 0xb6, 0xe0, 0x14, 0x47, 0xf4, 0x29, 0x0b, 0x6a, - 0xea, 0x32, 0x14, 0x71, 0x15, 0xe0, 0x4a, 0xb1, 0xb5, 0x17, 0xb9, 0x5f, 0x53, 0x1d, 0x84, 0xa8, - 0x22, 0x78, 0x58, 0x33, 0x46, 0xb1, 0xf2, 0x1d, 0x0f, 0x1d, 0x8f, 0xef, 0x18, 0x72, 0xfc, 0xc6, - 0xef, 0x86, 0x5a, 0xdb, 0xf1, 0xdd, 0x75, 0x12, 0x27, 0xdc, 0x9d, 0x2b, 0x4b, 0xc2, 0xca, 0x46, - 0xac, 0xe1, 0x54, 0x21, 0xc7, 0xec, 0xc5, 0x12, 0xc3, 0xff, 0xca, 0x14, 0xf2, 0x8a, 0x6e, 0xc6, - 0x26, 0x8e, 0xe9, 0x2c, 0x86, 0x07, 0xea, 0x2c, 0x1e, 0xde, 0xdb, 0x59, 0x6c, 0xff, 0x43, 0x0b, - 0xce, 0xe6, 0x7e, 0xb5, 0x87, 0x37, 0xf0, 0xd1, 0xfe, 0x52, 0x05, 0x4e, 0xe7, 0x54, 0xf9, 0x44, - 0x3b, 0xe6, 0x7c, 0xb6, 0x8a, 0x88, 0x21, 0x48, 0x1f, 0x89, 0xcb, 0x61, 0xcc, 0x99, 0xc4, 0x07, - 0x3b, 0xaa, 0xd1, 0xc7, 0x25, 0xe5, 0xfb, 0x7b, 0x5c, 0x62, 0x4c, 0xcb, 0x81, 0x07, 0x3a, 0x2d, - 0x2b, 0xfb, 0x9c, 0x61, 0xfc, 0x9a, 0x05, 0xe3, 0xed, 0x1e, 0xa5, 0xe5, 0x85, 0xe3, 0xf1, 0xe6, - 0xf1, 0x14, 0xae, 0xaf, 0x3f, 0x76, 0x77, 0x77, 0xa2, 0x67, 0x45, 0x7f, 0xdc, 0xb3, 0x57, 0xf6, - 0xb7, 0xcb, 0xc0, 0x4a, 0xcc, 0xb2, 0x4a, 0x6e, 0x3b, 0xe8, 0x63, 0x66, 0xb1, 0x60, 0xab, 0xa8, - 0xc2, 0xb6, 0x9c, 0xb8, 0x2a, 0x36, 0xcc, 0x47, 0x30, 0xaf, 0xf6, 0x70, 0x56, 0x68, 0x95, 0xfa, - 0x10, 0x5a, 0x9e, 0xac, 0xca, 0x5c, 0x2e, 0xbe, 0x2a, 0x73, 0x2d, 0x5b, 0x91, 0x79, 0xef, 0x4f, - 0x3c, 0xf0, 0x50, 0x7e, 0xe2, 0xbf, 0x65, 0x71, 0xc1, 0x93, 0xf9, 0x0a, 0xda, 0x32, 0xb0, 0xf6, - 0xb0, 0x0c, 0x9e, 0x81, 0x6a, 0x4c, 0xbc, 0xf5, 0x2b, 0xc4, 0xf1, 0x84, 0x05, 0xa1, 0xcf, 0xaf, - 0x45, 0x3b, 0x56, 0x18, 0xec, 0xda, 0x56, 0xcf, 0x0b, 0xee, 0x5c, 0x6a, 0x87, 0xc9, 0x8e, 0xb0, - 0x25, 0xf4, 0xb5, 0xad, 0x0a, 0x82, 0x0d, 0x2c, 0xfb, 0x6f, 0x97, 0xf8, 0x0c, 0x14, 0x41, 0x10, - 0x2f, 0x66, 0x2e, 0xda, 0xeb, 0x3f, 0x7e, 0xe0, 0x23, 0x00, 0x0d, 0x75, 0x45, 0xbd, 0x38, 0x13, - 0xba, 0x72, 0xe4, 0xfb, 0xb3, 0x05, 0x3d, 0xfd, 0x1a, 0xba, 0x0d, 0x1b, 0xfc, 0x52, 0xb2, 0xb4, - 0xbc, 0xaf, 0x2c, 0x4d, 0x89, 0x95, 0x81, 0x7d, 0xb4, 0xdd, 0x9f, 0x5a, 0x90, 0xb2, 0x88, 0x50, - 0x08, 0x15, 0xda, 0xdd, 0x9d, 0x62, 0x6e, 0xdf, 0x37, 0x49, 0x53, 0xd1, 0x28, 0xa6, 0x3d, 0xfb, - 0x89, 0x39, 0x23, 0xe4, 0x89, 0x58, 0x09, 0x3e, 0xaa, 0xd7, 0x8b, 0x63, 0x78, 0x25, 0x08, 0x36, - 0xf9, 0xc1, 0xa6, 0x8e, 0xbb, 0xb0, 0x5f, 0x84, 0x53, 0x5d, 0x9d, 0x62, 0x77, 0x6a, 0x05, 0x54, - 0xfb, 0x64, 0xa6, 0x2b, 0x4b, 0xe0, 0xc4, 0x1c, 0x66, 0x7f, 0xdd, 0x82, 0x93, 0x59, 0xf2, 0xe8, - 0x2d, 0x0b, 0x4e, 0xc5, 0x59, 0x7a, 0xc7, 0x35, 0x76, 0x2a, 0xde, 0xb1, 0x0b, 0x84, 0xbb, 0x3b, - 0x61, 0xff, 0x5f, 0x31, 0xf9, 0x6f, 0xb9, 0x7e, 0x33, 0xb8, 0xa3, 0x0c, 0x13, 0xab, 0xa7, 0x61, - 0x42, 0xd7, 0x63, 0x63, 0x83, 0x34, 0x3b, 0x5e, 0x57, 0xe6, 0xe8, 0x8a, 0x68, 0xc7, 0x0a, 0x83, - 0x25, 0xca, 0x75, 0x44, 0xd9, 0xf6, 0xcc, 0xa4, 0x9c, 0x15, 0xed, 0x58, 0x61, 0xa0, 0xe7, 0x61, - 0xc4, 0x78, 0x49, 0x39, 0x2f, 0x99, 0x41, 0x6e, 0xa8, 0xcc, 0x18, 0xa7, 0xb0, 0xd0, 0x24, 0x80, - 0x32, 0x72, 0xa4, 0x8a, 0x64, 0x8e, 0x22, 0x25, 0x89, 0x62, 0x6c, 0x60, 0xb0, 0xb4, 0x54, 0xaf, - 0x13, 0x33, 0x1f, 0xff, 0xa0, 0x2e, 0x25, 0x3a, 0x23, 0xda, 0xb0, 0x82, 0x52, 0x69, 0xd2, 0x76, - 0xfc, 0x8e, 0xe3, 0xd1, 0x11, 0x12, 0x5b, 0x3f, 0xb5, 0x0c, 0x17, 0x15, 0x04, 0x1b, 0x58, 0xf4, - 0x8d, 0x13, 0xb7, 0x4d, 0x5e, 0x09, 0x7c, 0x19, 0xa7, 0xa6, 0x8f, 0x7d, 0x44, 0x3b, 0x56, 0x18, - 0xf6, 0x7f, 0xb5, 0xe0, 0x84, 0x4e, 0x72, 0xe7, 0xb7, 0x67, 0x9b, 0x3b, 0x55, 0x6b, 0xdf, 0x9d, - 0x6a, 0x3a, 0xfb, 0xb7, 0xd4, 0x57, 0xf6, 0xaf, 0x99, 0x98, 0x5b, 0xde, 0x33, 0x31, 0xf7, 0xfb, - 0xf5, 0xcd, 0xac, 0x3c, 0x83, 0x77, 0x38, 0xef, 0x56, 0x56, 0x64, 0xc3, 0x60, 0xc3, 0x51, 0x15, - 0x5e, 0x46, 0xf8, 0xde, 0x61, 0x66, 0x9a, 0x21, 0x09, 0x88, 0xbd, 0x04, 0x35, 0x75, 0xfa, 0x21, - 0x37, 0xaa, 0x56, 0xfe, 0x46, 0xb5, 0xaf, 0x04, 0xc1, 0xfa, 0xda, 0x37, 0xbe, 0xf3, 0xc4, 0x3b, - 0x7e, 0xef, 0x3b, 0x4f, 0xbc, 0xe3, 0x8f, 0xbe, 0xf3, 0xc4, 0x3b, 0x3e, 0x7e, 0xf7, 0x09, 0xeb, - 0x1b, 0x77, 0x9f, 0xb0, 0x7e, 0xef, 0xee, 0x13, 0xd6, 0x1f, 0xdd, 0x7d, 0xc2, 0xfa, 0xf6, 0xdd, - 0x27, 0xac, 0x2f, 0xfc, 0xa7, 0x27, 0xde, 0xf1, 0x4a, 0x6e, 0xa0, 0x22, 0xfd, 0xf1, 0x6c, 0xa3, - 0x39, 0xb5, 0x75, 0x91, 0xc5, 0xca, 0xd1, 0xe5, 0x35, 0x65, 0xcc, 0xa9, 0x29, 0xb9, 0xbc, 0xfe, - 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2b, 0x8e, 0xba, 0x30, 0x2f, 0xe1, 0x00, 0x00, + // 11006 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x6d, 0x70, 0x24, 0xc7, + 0x75, 0x98, 0x66, 0x17, 0x0b, 0xec, 0x3e, 0x7c, 0xdc, 0x5d, 0xdf, 0x1d, 0x09, 0x9e, 0x48, 0xe2, + 0x3c, 0x8c, 0x29, 0x2a, 0x22, 0x01, 0xf3, 0x44, 0xca, 0x8c, 0x68, 0x4b, 0xc6, 0x02, 0x77, 0x38, + 0xdc, 0x01, 0x07, 0xb0, 0x81, 0xbb, 0x93, 0x28, 0x53, 0xd4, 0x60, 0xb7, 0xb1, 0x98, 0xc3, 0xec, + 0xcc, 0x70, 0x66, 0x16, 0x07, 0xd0, 0x92, 0x2c, 0x59, 0xb2, 0xad, 0x44, 0x1f, 0x54, 0xa4, 0xa4, + 0x4c, 0x27, 0x96, 0x22, 0x5b, 0x4e, 0xca, 0xae, 0x44, 0x15, 0x27, 0xf9, 0x11, 0x27, 0x4e, 0xca, + 0x65, 0x3b, 0x95, 0x52, 0x4a, 0x49, 0xd9, 0xe5, 0x72, 0x59, 0x4e, 0x62, 0x23, 0xd2, 0xa5, 0x52, + 0x49, 0xa5, 0x2a, 0xae, 0x72, 0xe2, 0x1f, 0xc9, 0x25, 0x3f, 0x52, 0xfd, 0xdd, 0x33, 0x3b, 0x0b, + 0x2c, 0x80, 0xc1, 0xdd, 0x49, 0xe6, 0xbf, 0xdd, 0x7e, 0x6f, 0xde, 0xeb, 0xe9, 0xe9, 0x7e, 0xef, + 0xf5, 0xeb, 0xf7, 0x5e, 0xc3, 0x42, 0xcb, 0x4d, 0x36, 0x3a, 0x6b, 0x93, 0x8d, 0xa0, 0x3d, 0xe5, + 0x44, 0xad, 0x20, 0x8c, 0x82, 0x5b, 0xec, 0xc7, 0x33, 0x8d, 0xe6, 0xd4, 0xd6, 0x85, 0xa9, 0x70, + 0xb3, 0x35, 0xe5, 0x84, 0x6e, 0x3c, 0xe5, 0x84, 0xa1, 0xe7, 0x36, 0x9c, 0xc4, 0x0d, 0xfc, 0xa9, + 0xad, 0x67, 0x1d, 0x2f, 0xdc, 0x70, 0x9e, 0x9d, 0x6a, 0x11, 0x9f, 0x44, 0x4e, 0x42, 0x9a, 0x93, + 0x61, 0x14, 0x24, 0x01, 0xfa, 0x11, 0x4d, 0x6d, 0x52, 0x52, 0x63, 0x3f, 0x5e, 0x6d, 0x34, 0x27, + 0xb7, 0x2e, 0x4c, 0x86, 0x9b, 0xad, 0x49, 0x4a, 0x6d, 0xd2, 0xa0, 0x36, 0x29, 0xa9, 0x9d, 0x7b, + 0xc6, 0xe8, 0x4b, 0x2b, 0x68, 0x05, 0x53, 0x8c, 0xe8, 0x5a, 0x67, 0x9d, 0xfd, 0x63, 0x7f, 0xd8, + 0x2f, 0xce, 0xec, 0x9c, 0xbd, 0xf9, 0x42, 0x3c, 0xe9, 0x06, 0xb4, 0x7b, 0x53, 0x8d, 0x20, 0x22, + 0x53, 0x5b, 0x5d, 0x1d, 0x3a, 0x77, 0x59, 0xe3, 0x90, 0xed, 0x84, 0xf8, 0xb1, 0x1b, 0xf8, 0xf1, + 0x33, 0xb4, 0x0b, 0x24, 0xda, 0x22, 0x91, 0xf9, 0x7a, 0x06, 0x42, 0x1e, 0xa5, 0xe7, 0x34, 0xa5, + 0xb6, 0xd3, 0xd8, 0x70, 0x7d, 0x12, 0xed, 0xe8, 0xc7, 0xdb, 0x24, 0x71, 0xf2, 0x9e, 0x9a, 0xea, + 0xf5, 0x54, 0xd4, 0xf1, 0x13, 0xb7, 0x4d, 0xba, 0x1e, 0x78, 0xcf, 0x7e, 0x0f, 0xc4, 0x8d, 0x0d, + 0xd2, 0x76, 0xba, 0x9e, 0x7b, 0x77, 0xaf, 0xe7, 0x3a, 0x89, 0xeb, 0x4d, 0xb9, 0x7e, 0x12, 0x27, + 0x51, 0xf6, 0x21, 0xfb, 0x17, 0x2c, 0x18, 0x9d, 0xbe, 0xb9, 0x32, 0xdd, 0x49, 0x36, 0x66, 0x02, + 0x7f, 0xdd, 0x6d, 0xa1, 0xe7, 0x61, 0xb8, 0xe1, 0x75, 0xe2, 0x84, 0x44, 0xd7, 0x9c, 0x36, 0x19, + 0xb7, 0xce, 0x5b, 0x4f, 0xd5, 0xea, 0xa7, 0xbf, 0xb9, 0x3b, 0xf1, 0xb6, 0x3b, 0xbb, 0x13, 0xc3, + 0x33, 0x1a, 0x84, 0x4d, 0x3c, 0xf4, 0x4e, 0x18, 0x8a, 0x02, 0x8f, 0x4c, 0xe3, 0x6b, 0xe3, 0x25, + 0xf6, 0xc8, 0x09, 0xf1, 0xc8, 0x10, 0xe6, 0xcd, 0x58, 0xc2, 0x29, 0x6a, 0x18, 0x05, 0xeb, 0xae, + 0x47, 0xc6, 0xcb, 0x69, 0xd4, 0x65, 0xde, 0x8c, 0x25, 0xdc, 0xfe, 0xc3, 0x12, 0xc0, 0x74, 0x18, + 0x2e, 0x47, 0xc1, 0x2d, 0xd2, 0x48, 0xd0, 0x47, 0xa0, 0x4a, 0x87, 0xb9, 0xe9, 0x24, 0x0e, 0xeb, + 0xd8, 0xf0, 0x85, 0x1f, 0x9a, 0xe4, 0x6f, 0x3d, 0x69, 0xbe, 0xb5, 0x9e, 0x64, 0x14, 0x7b, 0x72, + 0xeb, 0xd9, 0xc9, 0xa5, 0x35, 0xfa, 0xfc, 0x22, 0x49, 0x9c, 0x3a, 0x12, 0xcc, 0x40, 0xb7, 0x61, + 0x45, 0x15, 0xf9, 0x30, 0x10, 0x87, 0xa4, 0xc1, 0xde, 0x61, 0xf8, 0xc2, 0xc2, 0xe4, 0x51, 0x66, + 0xf3, 0xa4, 0xee, 0xf9, 0x4a, 0x48, 0x1a, 0xf5, 0x11, 0xc1, 0x79, 0x80, 0xfe, 0xc3, 0x8c, 0x0f, + 0xda, 0x82, 0xc1, 0x38, 0x71, 0x92, 0x4e, 0xcc, 0x86, 0x62, 0xf8, 0xc2, 0xb5, 0xc2, 0x38, 0x32, + 0xaa, 0xf5, 0x31, 0xc1, 0x73, 0x90, 0xff, 0xc7, 0x82, 0x9b, 0xfd, 0x27, 0x16, 0x8c, 0x69, 0xe4, + 0x05, 0x37, 0x4e, 0xd0, 0x8f, 0x77, 0x0d, 0xee, 0x64, 0x7f, 0x83, 0x4b, 0x9f, 0x66, 0x43, 0x7b, + 0x52, 0x30, 0xab, 0xca, 0x16, 0x63, 0x60, 0xdb, 0x50, 0x71, 0x13, 0xd2, 0x8e, 0xc7, 0x4b, 0xe7, + 0xcb, 0x4f, 0x0d, 0x5f, 0xb8, 0x5c, 0xd4, 0x7b, 0xd6, 0x47, 0x05, 0xd3, 0xca, 0x3c, 0x25, 0x8f, + 0x39, 0x17, 0xfb, 0x57, 0x47, 0xcc, 0xf7, 0xa3, 0x03, 0x8e, 0x9e, 0x85, 0xe1, 0x38, 0xe8, 0x44, + 0x0d, 0x82, 0x49, 0x18, 0xc4, 0xe3, 0xd6, 0xf9, 0x32, 0x9d, 0x7a, 0x74, 0x52, 0xaf, 0xe8, 0x66, + 0x6c, 0xe2, 0xa0, 0x2f, 0x58, 0x30, 0xd2, 0x24, 0x71, 0xe2, 0xfa, 0x8c, 0xbf, 0xec, 0xfc, 0xea, + 0x91, 0x3b, 0x2f, 0x1b, 0x67, 0x35, 0xf1, 0xfa, 0x19, 0xf1, 0x22, 0x23, 0x46, 0x63, 0x8c, 0x53, + 0xfc, 0xe9, 0xe2, 0x6c, 0x92, 0xb8, 0x11, 0xb9, 0x21, 0xfd, 0x2f, 0x96, 0x8f, 0x5a, 0x9c, 0xb3, + 0x1a, 0x84, 0x4d, 0x3c, 0xe4, 0x43, 0x85, 0x2e, 0xbe, 0x78, 0x7c, 0x80, 0xf5, 0x7f, 0xfe, 0x68, + 0xfd, 0x17, 0x83, 0x4a, 0xd7, 0xb5, 0x1e, 0x7d, 0xfa, 0x2f, 0xc6, 0x9c, 0x0d, 0xfa, 0xbc, 0x05, + 0xe3, 0x42, 0x38, 0x60, 0xc2, 0x07, 0xf4, 0xe6, 0x86, 0x9b, 0x10, 0xcf, 0x8d, 0x93, 0xf1, 0x0a, + 0xeb, 0xc3, 0x54, 0x7f, 0x73, 0x6b, 0x2e, 0x0a, 0x3a, 0xe1, 0x55, 0xd7, 0x6f, 0xd6, 0xcf, 0x0b, + 0x4e, 0xe3, 0x33, 0x3d, 0x08, 0xe3, 0x9e, 0x2c, 0xd1, 0x97, 0x2d, 0x38, 0xe7, 0x3b, 0x6d, 0x12, + 0x87, 0x0e, 0xfd, 0xb4, 0x1c, 0x5c, 0xf7, 0x9c, 0xc6, 0x26, 0xeb, 0xd1, 0xe0, 0xe1, 0x7a, 0x64, + 0x8b, 0x1e, 0x9d, 0xbb, 0xd6, 0x93, 0x34, 0xde, 0x83, 0x2d, 0xfa, 0xba, 0x05, 0xa7, 0x82, 0x28, + 0xdc, 0x70, 0x7c, 0xd2, 0x94, 0xd0, 0x78, 0x7c, 0x88, 0x2d, 0xbd, 0x0f, 0x1f, 0xed, 0x13, 0x2d, + 0x65, 0xc9, 0x2e, 0x06, 0xbe, 0x9b, 0x04, 0xd1, 0x0a, 0x49, 0x12, 0xd7, 0x6f, 0xc5, 0xf5, 0xb3, + 0x77, 0x76, 0x27, 0x4e, 0x75, 0x61, 0xe1, 0xee, 0xfe, 0xa0, 0x9f, 0x80, 0xe1, 0x78, 0xc7, 0x6f, + 0xdc, 0x74, 0xfd, 0x66, 0x70, 0x3b, 0x1e, 0xaf, 0x16, 0xb1, 0x7c, 0x57, 0x14, 0x41, 0xb1, 0x00, + 0x35, 0x03, 0x6c, 0x72, 0xcb, 0xff, 0x70, 0x7a, 0x2a, 0xd5, 0x8a, 0xfe, 0x70, 0x7a, 0x32, 0xed, + 0xc1, 0x16, 0xfd, 0xac, 0x05, 0xa3, 0xb1, 0xdb, 0xf2, 0x9d, 0xa4, 0x13, 0x91, 0xab, 0x64, 0x27, + 0x1e, 0x07, 0xd6, 0x91, 0x2b, 0x47, 0x1c, 0x15, 0x83, 0x64, 0xfd, 0xac, 0xe8, 0xe3, 0xa8, 0xd9, + 0x1a, 0xe3, 0x34, 0xdf, 0xbc, 0x85, 0xa6, 0xa7, 0xf5, 0x70, 0xb1, 0x0b, 0x4d, 0x4f, 0xea, 0x9e, + 0x2c, 0xd1, 0x8f, 0xc1, 0x49, 0xde, 0xa4, 0x46, 0x36, 0x1e, 0x1f, 0x61, 0x82, 0xf6, 0xcc, 0x9d, + 0xdd, 0x89, 0x93, 0x2b, 0x19, 0x18, 0xee, 0xc2, 0x46, 0xaf, 0xc1, 0x44, 0x48, 0xa2, 0xb6, 0x9b, + 0x2c, 0xf9, 0xde, 0x8e, 0x14, 0xdf, 0x8d, 0x20, 0x24, 0x4d, 0xd1, 0x9d, 0x78, 0x7c, 0xf4, 0xbc, + 0xf5, 0x54, 0xb5, 0xfe, 0x0e, 0xd1, 0xcd, 0x89, 0xe5, 0xbd, 0xd1, 0xf1, 0x7e, 0xf4, 0xec, 0x7f, + 0x53, 0x82, 0x93, 0x59, 0xc5, 0x89, 0xfe, 0x9e, 0x05, 0x27, 0x6e, 0xdd, 0x4e, 0x56, 0x83, 0x4d, + 0xe2, 0xc7, 0xf5, 0x1d, 0x2a, 0xde, 0x98, 0xca, 0x18, 0xbe, 0xd0, 0x28, 0x56, 0x45, 0x4f, 0x5e, + 0x49, 0x73, 0xb9, 0xe8, 0x27, 0xd1, 0x4e, 0xfd, 0x61, 0xf1, 0x76, 0x27, 0xae, 0xdc, 0x5c, 0x35, + 0xa1, 0x38, 0xdb, 0xa9, 0x73, 0x9f, 0xb5, 0xe0, 0x4c, 0x1e, 0x09, 0x74, 0x12, 0xca, 0x9b, 0x64, + 0x87, 0x1b, 0x70, 0x98, 0xfe, 0x44, 0xaf, 0x40, 0x65, 0xcb, 0xf1, 0x3a, 0x44, 0x58, 0x37, 0x73, + 0x47, 0x7b, 0x11, 0xd5, 0x33, 0xcc, 0xa9, 0xbe, 0xb7, 0xf4, 0x82, 0x65, 0xff, 0x6e, 0x19, 0x86, + 0x0d, 0xfd, 0x76, 0x0f, 0x2c, 0xb6, 0x20, 0x65, 0xb1, 0x2d, 0x16, 0xa6, 0x9a, 0x7b, 0x9a, 0x6c, + 0xb7, 0x33, 0x26, 0xdb, 0x52, 0x71, 0x2c, 0xf7, 0xb4, 0xd9, 0x50, 0x02, 0xb5, 0x20, 0xa4, 0xd6, + 0x3b, 0x55, 0xfd, 0x03, 0x45, 0x7c, 0xc2, 0x25, 0x49, 0xae, 0x3e, 0x7a, 0x67, 0x77, 0xa2, 0xa6, + 0xfe, 0x62, 0xcd, 0xc8, 0xfe, 0xb6, 0x05, 0x67, 0x8c, 0x3e, 0xce, 0x04, 0x7e, 0xd3, 0x65, 0x9f, + 0xf6, 0x3c, 0x0c, 0x24, 0x3b, 0xa1, 0xdc, 0x21, 0xa8, 0x91, 0x5a, 0xdd, 0x09, 0x09, 0x66, 0x10, + 0x6a, 0xe8, 0xb7, 0x49, 0x1c, 0x3b, 0x2d, 0x92, 0xdd, 0x13, 0x2c, 0xf2, 0x66, 0x2c, 0xe1, 0x28, + 0x02, 0xe4, 0x39, 0x71, 0xb2, 0x1a, 0x39, 0x7e, 0xcc, 0xc8, 0xaf, 0xba, 0x6d, 0x22, 0x06, 0xf8, + 0x2f, 0xf7, 0x37, 0x63, 0xe8, 0x13, 0xf5, 0x87, 0xee, 0xec, 0x4e, 0xa0, 0x85, 0x2e, 0x4a, 0x38, + 0x87, 0xba, 0xfd, 0x65, 0x0b, 0x1e, 0xca, 0xb7, 0xc5, 0xd0, 0x93, 0x30, 0xc8, 0xb7, 0x87, 0xe2, + 0xed, 0xf4, 0x27, 0x61, 0xad, 0x58, 0x40, 0xd1, 0x14, 0xd4, 0x94, 0x9e, 0x10, 0xef, 0x78, 0x4a, + 0xa0, 0xd6, 0xb4, 0x72, 0xd1, 0x38, 0x74, 0xd0, 0xe8, 0x1f, 0x61, 0xb9, 0xa9, 0x41, 0x63, 0xfb, + 0x29, 0x06, 0xb1, 0xff, 0x93, 0x05, 0x27, 0x8c, 0x5e, 0xdd, 0x03, 0xd3, 0xdc, 0x4f, 0x9b, 0xe6, + 0xf3, 0x85, 0xcd, 0xe7, 0x1e, 0xb6, 0xf9, 0xe7, 0x2d, 0x38, 0x67, 0x60, 0x2d, 0x3a, 0x49, 0x63, + 0xe3, 0xe2, 0x76, 0x18, 0x91, 0x98, 0x6e, 0xbd, 0xd1, 0x63, 0x86, 0xdc, 0xaa, 0x0f, 0x0b, 0x0a, + 0xe5, 0xab, 0x64, 0x87, 0x0b, 0xb1, 0xa7, 0xa1, 0xca, 0x27, 0x67, 0x10, 0x89, 0x11, 0x57, 0xef, + 0xb6, 0x24, 0xda, 0xb1, 0xc2, 0x40, 0x36, 0x0c, 0x32, 0xe1, 0x44, 0x17, 0x2b, 0x55, 0x43, 0x40, + 0x3f, 0xe2, 0x0d, 0xd6, 0x82, 0x05, 0xc4, 0x8e, 0x53, 0xdd, 0x59, 0x8e, 0x08, 0xfb, 0xb8, 0xcd, + 0x4b, 0x2e, 0xf1, 0x9a, 0x31, 0xdd, 0x36, 0x38, 0xbe, 0x1f, 0x24, 0x62, 0x07, 0x60, 0x6c, 0x1b, + 0xa6, 0x75, 0x33, 0x36, 0x71, 0x28, 0x53, 0xcf, 0x59, 0x23, 0x1e, 0x1f, 0x51, 0xc1, 0x74, 0x81, + 0xb5, 0x60, 0x01, 0xb1, 0xef, 0x94, 0xd8, 0x06, 0x45, 0x2d, 0x7d, 0x72, 0x2f, 0x76, 0xb7, 0x51, + 0x4a, 0x56, 0x2e, 0x17, 0x27, 0xb8, 0x48, 0xef, 0x1d, 0xee, 0xeb, 0x19, 0x71, 0x89, 0x0b, 0xe5, + 0xba, 0xf7, 0x2e, 0xf7, 0xb7, 0x4a, 0x30, 0x91, 0x7e, 0xa0, 0x4b, 0xda, 0xd2, 0x2d, 0x95, 0xc1, + 0x28, 0xeb, 0xef, 0x30, 0xf0, 0xb1, 0x89, 0xd7, 0x43, 0x60, 0x95, 0x8e, 0x53, 0x60, 0x99, 0xf2, + 0xb4, 0xbc, 0x8f, 0x3c, 0x7d, 0x52, 0x8d, 0xfa, 0x40, 0x46, 0x80, 0xa5, 0x75, 0xca, 0x79, 0x18, + 0x88, 0x13, 0x12, 0x8e, 0x57, 0xd2, 0xf2, 0x68, 0x25, 0x21, 0x21, 0x66, 0x10, 0xfb, 0xbf, 0x97, + 0xe0, 0xe1, 0xf4, 0x18, 0x6a, 0x15, 0xf0, 0xfe, 0x94, 0x0a, 0x78, 0x97, 0xa9, 0x02, 0xee, 0xee, + 0x4e, 0xbc, 0xbd, 0xc7, 0x63, 0xdf, 0x33, 0x1a, 0x02, 0xcd, 0x65, 0x46, 0x71, 0x2a, 0x3d, 0x8a, + 0x77, 0x77, 0x27, 0x1e, 0xeb, 0xf1, 0x8e, 0x99, 0x61, 0x7e, 0x12, 0x06, 0x23, 0xe2, 0xc4, 0x81, + 0x2f, 0x06, 0x5a, 0x7d, 0x0e, 0xcc, 0x5a, 0xb1, 0x80, 0xda, 0xbf, 0x5f, 0xcb, 0x0e, 0xf6, 0x1c, + 0x77, 0xd8, 0x05, 0x11, 0x72, 0x61, 0x80, 0x99, 0xf5, 0x5c, 0x34, 0x5c, 0x3d, 0xda, 0x32, 0xa2, + 0x6a, 0x40, 0x91, 0xae, 0x57, 0xe9, 0x57, 0xa3, 0x4d, 0x98, 0xb1, 0x40, 0xdb, 0x50, 0x6d, 0x48, + 0x6b, 0xbb, 0x54, 0x84, 0x5f, 0x4a, 0xd8, 0xda, 0x9a, 0xe3, 0x08, 0x95, 0xd7, 0xca, 0x44, 0x57, + 0xdc, 0x10, 0x81, 0x72, 0xcb, 0x4d, 0xc4, 0x67, 0x3d, 0xe2, 0x7e, 0x6a, 0xce, 0x35, 0x5e, 0x71, + 0x88, 0x2a, 0x91, 0x39, 0x37, 0xc1, 0x94, 0x3e, 0xfa, 0x69, 0x0b, 0x86, 0xe3, 0x46, 0x7b, 0x39, + 0x0a, 0xb6, 0xdc, 0x26, 0x89, 0x84, 0x35, 0x75, 0x44, 0xd1, 0xb4, 0x32, 0xb3, 0x28, 0x09, 0x6a, + 0xbe, 0x7c, 0x7f, 0xab, 0x21, 0xd8, 0xe4, 0x4b, 0x77, 0x19, 0x0f, 0x8b, 0x77, 0x9f, 0x25, 0x0d, + 0x97, 0xea, 0x3f, 0xb9, 0xa9, 0x62, 0x33, 0xe5, 0xc8, 0xd6, 0xe5, 0x6c, 0xa7, 0xb1, 0x49, 0xd7, + 0x9b, 0xee, 0xd0, 0xdb, 0xef, 0xec, 0x4e, 0x3c, 0x3c, 0x93, 0xcf, 0x13, 0xf7, 0xea, 0x0c, 0x1b, + 0xb0, 0xb0, 0xe3, 0x79, 0x98, 0xbc, 0xd6, 0x21, 0xcc, 0x65, 0x52, 0xc0, 0x80, 0x2d, 0x6b, 0x82, + 0x99, 0x01, 0x33, 0x20, 0xd8, 0xe4, 0x8b, 0x5e, 0x83, 0xc1, 0xb6, 0x93, 0x44, 0xee, 0xb6, 0xf0, + 0x93, 0x1c, 0xd1, 0xde, 0x5f, 0x64, 0xb4, 0x34, 0x73, 0xa6, 0xa9, 0x79, 0x23, 0x16, 0x8c, 0x50, + 0x1b, 0x2a, 0x6d, 0x12, 0xb5, 0xc8, 0x78, 0xb5, 0x08, 0x9f, 0xf0, 0x22, 0x25, 0xa5, 0x19, 0xd6, + 0xa8, 0x75, 0xc4, 0xda, 0x30, 0xe7, 0x82, 0x5e, 0x81, 0x6a, 0x4c, 0x3c, 0xd2, 0xa0, 0xf6, 0x4d, + 0x8d, 0x71, 0x7c, 0x77, 0x9f, 0xb6, 0x1e, 0x35, 0x2c, 0x56, 0xc4, 0xa3, 0x7c, 0x81, 0xc9, 0x7f, + 0x58, 0x91, 0xa4, 0x03, 0x18, 0x7a, 0x9d, 0x96, 0xeb, 0x8f, 0x43, 0x11, 0x03, 0xb8, 0xcc, 0x68, + 0x65, 0x06, 0x90, 0x37, 0x62, 0xc1, 0xc8, 0xfe, 0x2f, 0x16, 0xa0, 0xb4, 0x50, 0xbb, 0x07, 0x46, + 0xed, 0x6b, 0x69, 0xa3, 0x76, 0xa1, 0x48, 0xab, 0xa3, 0x87, 0x5d, 0xfb, 0x1b, 0x35, 0xc8, 0xa8, + 0x83, 0x6b, 0x24, 0x4e, 0x48, 0xf3, 0x2d, 0x11, 0xfe, 0x96, 0x08, 0x7f, 0x4b, 0x84, 0x2b, 0x11, + 0xbe, 0x96, 0x11, 0xe1, 0xef, 0x33, 0x56, 0xbd, 0x3e, 0x80, 0x7d, 0x55, 0x9d, 0xd0, 0x9a, 0x3d, + 0x30, 0x10, 0xa8, 0x24, 0xb8, 0xb2, 0xb2, 0x74, 0x2d, 0x57, 0x66, 0xbf, 0x9a, 0x96, 0xd9, 0x47, + 0x65, 0xf1, 0x17, 0x41, 0x4a, 0xff, 0x6b, 0x0b, 0xde, 0x91, 0x96, 0x5e, 0x72, 0xe6, 0xcc, 0xb7, + 0xfc, 0x20, 0x22, 0xb3, 0xee, 0xfa, 0x3a, 0x89, 0x88, 0xdf, 0x20, 0xb1, 0xf2, 0x62, 0x58, 0xbd, + 0xbc, 0x18, 0xe8, 0x39, 0x18, 0xb9, 0x15, 0x07, 0xfe, 0x72, 0xe0, 0xfa, 0x42, 0x04, 0xd1, 0x8d, + 0xf0, 0xc9, 0x3b, 0xbb, 0x13, 0x23, 0x74, 0x44, 0x65, 0x3b, 0x4e, 0x61, 0xa1, 0x19, 0x38, 0x75, + 0xeb, 0xb5, 0x65, 0x27, 0x31, 0xdc, 0x01, 0x72, 0xe3, 0xce, 0x0e, 0x2c, 0xae, 0xbc, 0x94, 0x01, + 0xe2, 0x6e, 0x7c, 0xfb, 0x6f, 0x97, 0xe0, 0x91, 0xcc, 0x8b, 0x04, 0x9e, 0x17, 0x74, 0x12, 0xba, + 0xa9, 0x41, 0x5f, 0xb5, 0xe0, 0x64, 0x3b, 0xed, 0x71, 0x88, 0x85, 0x63, 0xf7, 0x03, 0x85, 0xe9, + 0x88, 0x8c, 0x4b, 0xa3, 0x3e, 0x2e, 0x46, 0xe8, 0x64, 0x06, 0x10, 0xe3, 0xae, 0xbe, 0xa0, 0x57, + 0xa0, 0xd6, 0x76, 0xb6, 0xaf, 0x87, 0x4d, 0x27, 0x91, 0xfb, 0xc9, 0xde, 0x6e, 0x80, 0x4e, 0xe2, + 0x7a, 0x93, 0xfc, 0x68, 0x7f, 0x72, 0xde, 0x4f, 0x96, 0xa2, 0x95, 0x24, 0x72, 0xfd, 0x16, 0x77, + 0xe7, 0x2d, 0x4a, 0x32, 0x58, 0x53, 0xb4, 0xbf, 0x62, 0x65, 0x95, 0x94, 0x1a, 0x9d, 0xc8, 0x49, + 0x48, 0x6b, 0x07, 0x7d, 0x14, 0x2a, 0x74, 0xe3, 0x27, 0x47, 0xe5, 0x66, 0x91, 0x9a, 0xd3, 0xf8, + 0x12, 0x5a, 0x89, 0xd2, 0x7f, 0x31, 0xe6, 0x4c, 0xed, 0xaf, 0xd6, 0xb2, 0xc6, 0x02, 0x3b, 0xbc, + 0xbd, 0x00, 0xd0, 0x0a, 0x56, 0x49, 0x3b, 0xf4, 0xe8, 0xb0, 0x58, 0xec, 0x04, 0x40, 0xf9, 0x3a, + 0xe6, 0x14, 0x04, 0x1b, 0x58, 0xe8, 0xaf, 0x5a, 0x00, 0x2d, 0x39, 0xe7, 0xa5, 0x21, 0x70, 0xbd, + 0xc8, 0xd7, 0xd1, 0x2b, 0x4a, 0xf7, 0x45, 0x31, 0xc4, 0x06, 0x73, 0xf4, 0x53, 0x16, 0x54, 0x13, + 0xd9, 0x7d, 0xae, 0x1a, 0x57, 0x8b, 0xec, 0x89, 0x7c, 0x69, 0x6d, 0x13, 0xa9, 0x21, 0x51, 0x7c, + 0xd1, 0xcf, 0x58, 0x00, 0xf1, 0x8e, 0xdf, 0x58, 0x0e, 0x3c, 0xb7, 0xb1, 0x23, 0x34, 0xe6, 0x8d, + 0x42, 0xfd, 0x31, 0x8a, 0x7a, 0x7d, 0x8c, 0x8e, 0x86, 0xfe, 0x8f, 0x0d, 0xce, 0xe8, 0xe3, 0x50, + 0x8d, 0xc5, 0x74, 0x13, 0x3a, 0x72, 0xb5, 0x58, 0xaf, 0x10, 0xa7, 0x2d, 0xc4, 0xab, 0xf8, 0x87, + 0x15, 0x4f, 0xf4, 0x73, 0x16, 0x9c, 0x08, 0xd3, 0x7e, 0x3e, 0xa1, 0x0e, 0x8b, 0x93, 0x01, 0x19, + 0x3f, 0x62, 0xfd, 0xf4, 0x9d, 0xdd, 0x89, 0x13, 0x99, 0x46, 0x9c, 0xed, 0x05, 0x95, 0x80, 0x7a, + 0x06, 0x2f, 0x85, 0xdc, 0xe7, 0x38, 0xa4, 0x25, 0xe0, 0x5c, 0x16, 0x88, 0xbb, 0xf1, 0xd1, 0x32, + 0x9c, 0xa1, 0xbd, 0xdb, 0xe1, 0xe6, 0xa7, 0x54, 0x2f, 0x31, 0x53, 0x86, 0xd5, 0xfa, 0xa3, 0x62, + 0x86, 0x30, 0xaf, 0x7e, 0x16, 0x07, 0xe7, 0x3e, 0x89, 0x7e, 0xd7, 0x82, 0x47, 0x5d, 0xa6, 0x06, + 0x4c, 0x87, 0xb9, 0xd6, 0x08, 0xe2, 0x24, 0x96, 0x14, 0x2a, 0x2b, 0x7a, 0xa9, 0x9f, 0xfa, 0x5f, + 0x12, 0x6f, 0xf0, 0xe8, 0xfc, 0x1e, 0x5d, 0xc2, 0x7b, 0x76, 0x18, 0xfd, 0x30, 0x8c, 0xca, 0x75, + 0xb1, 0x4c, 0x45, 0x30, 0x53, 0xb4, 0xb5, 0xfa, 0xa9, 0x3b, 0xbb, 0x13, 0xa3, 0xab, 0x26, 0x00, + 0xa7, 0xf1, 0xec, 0x6f, 0x95, 0x52, 0xe7, 0x21, 0xca, 0x09, 0xc9, 0xc4, 0x4d, 0x43, 0xfa, 0x7f, + 0xa4, 0xf4, 0x2c, 0x54, 0xdc, 0x28, 0xef, 0x92, 0x16, 0x37, 0xaa, 0x29, 0xc6, 0x06, 0x73, 0x6a, + 0x94, 0x9e, 0x72, 0xb2, 0xae, 0x4e, 0x21, 0x01, 0x5f, 0x29, 0xb2, 0x4b, 0xdd, 0xa7, 0x57, 0x8f, + 0x88, 0xae, 0x9d, 0xea, 0x02, 0xe1, 0xee, 0x2e, 0xd9, 0xdf, 0x4a, 0x9f, 0xc1, 0x18, 0x8b, 0xb7, + 0x8f, 0xf3, 0xa5, 0x2f, 0x58, 0x30, 0x1c, 0x05, 0x9e, 0xe7, 0xfa, 0x2d, 0x2a, 0x68, 0x84, 0xb6, + 0xfc, 0xd0, 0xb1, 0x28, 0x2c, 0x21, 0x51, 0x98, 0x69, 0x8b, 0x35, 0x4f, 0x6c, 0x76, 0xc0, 0xfe, + 0x13, 0x0b, 0xc6, 0x7b, 0x09, 0x44, 0x44, 0xe0, 0xed, 0x72, 0xb5, 0xab, 0xe8, 0x8a, 0x25, 0x7f, + 0x96, 0x78, 0x44, 0x39, 0x9e, 0xab, 0xf5, 0x27, 0xc4, 0x6b, 0xbe, 0x7d, 0xb9, 0x37, 0x2a, 0xde, + 0x8b, 0x0e, 0x7a, 0x19, 0x4e, 0x1a, 0xef, 0x15, 0xab, 0x81, 0xa9, 0xd5, 0x27, 0xa9, 0x05, 0x32, + 0x9d, 0x81, 0xdd, 0xdd, 0x9d, 0x78, 0x28, 0xdb, 0x26, 0x24, 0x76, 0x17, 0x1d, 0xfb, 0x97, 0x4b, + 0xd9, 0xaf, 0xa5, 0x94, 0xed, 0x9b, 0x56, 0xd7, 0x76, 0xfe, 0x03, 0xc7, 0xa1, 0xe0, 0xd8, 0xc6, + 0x5f, 0x05, 0x70, 0xf4, 0xc6, 0xb9, 0x8f, 0x27, 0xc4, 0xf6, 0xbf, 0x1d, 0x80, 0x3d, 0x7a, 0xd6, + 0x87, 0xf5, 0x7c, 0xe0, 0x63, 0xc5, 0xcf, 0x59, 0xea, 0xc8, 0xa9, 0xcc, 0x16, 0x79, 0xf3, 0xb8, + 0xc6, 0x9e, 0x6f, 0x60, 0x62, 0x1e, 0xa5, 0xa0, 0xdc, 0xd8, 0xe9, 0xc3, 0x2d, 0xf4, 0x35, 0x2b, + 0x7d, 0x68, 0xc6, 0xc3, 0xce, 0xdc, 0x63, 0xeb, 0x93, 0x71, 0x12, 0xc7, 0x3b, 0xa6, 0xcf, 0x6f, + 0x7a, 0x9d, 0xd1, 0x4d, 0x02, 0xac, 0xbb, 0xbe, 0xe3, 0xb9, 0xaf, 0xd3, 0xed, 0x49, 0x85, 0x69, + 0x58, 0x66, 0xb2, 0x5c, 0x52, 0xad, 0xd8, 0xc0, 0x38, 0xf7, 0x57, 0x60, 0xd8, 0x78, 0xf3, 0x9c, + 0xe0, 0x8a, 0x33, 0x66, 0x70, 0x45, 0xcd, 0x88, 0x89, 0x38, 0xf7, 0x3e, 0x38, 0x99, 0xed, 0xe0, + 0x41, 0x9e, 0xb7, 0xff, 0xf7, 0x50, 0xf6, 0x14, 0x6b, 0x95, 0x44, 0x6d, 0xda, 0xb5, 0xb7, 0x3c, + 0x4b, 0x6f, 0x79, 0x96, 0xde, 0xf2, 0x2c, 0x99, 0x87, 0x03, 0xc2, 0x6b, 0x32, 0x74, 0x8f, 0xbc, + 0x26, 0x29, 0x3f, 0x50, 0xb5, 0x70, 0x3f, 0x90, 0x7d, 0xa7, 0x02, 0x29, 0x3b, 0x8a, 0x8f, 0xf7, + 0x3b, 0x61, 0x28, 0x22, 0x61, 0x70, 0x1d, 0x2f, 0x08, 0x1d, 0xa2, 0x63, 0xed, 0x79, 0x33, 0x96, + 0x70, 0xaa, 0x6b, 0x42, 0x27, 0xd9, 0x10, 0x4a, 0x44, 0xe9, 0x9a, 0x65, 0x27, 0xd9, 0xc0, 0x0c, + 0x82, 0xde, 0x07, 0x63, 0x89, 0x13, 0xb5, 0xa8, 0xbd, 0xbd, 0xc5, 0x3e, 0xab, 0x38, 0xeb, 0x7c, + 0x48, 0xe0, 0x8e, 0xad, 0xa6, 0xa0, 0x38, 0x83, 0x8d, 0x5e, 0x83, 0x81, 0x0d, 0xe2, 0xb5, 0xc5, + 0x90, 0xaf, 0x14, 0x27, 0xe3, 0xd9, 0xbb, 0x5e, 0x26, 0x5e, 0x9b, 0x4b, 0x20, 0xfa, 0x0b, 0x33, + 0x56, 0x74, 0xbe, 0xd5, 0x36, 0x3b, 0x71, 0x12, 0xb4, 0xdd, 0xd7, 0xa5, 0x8b, 0xef, 0x03, 0x05, + 0x33, 0xbe, 0x2a, 0xe9, 0x73, 0x5f, 0x8a, 0xfa, 0x8b, 0x35, 0x67, 0xd6, 0x8f, 0xa6, 0x1b, 0xb1, + 0x4f, 0xb5, 0x23, 0x3c, 0x75, 0x45, 0xf7, 0x63, 0x56, 0xd2, 0xe7, 0xfd, 0x50, 0x7f, 0xb1, 0xe6, + 0x8c, 0x76, 0xd4, 0xbc, 0x1f, 0x66, 0x7d, 0xb8, 0x5e, 0x70, 0x1f, 0xf8, 0x9c, 0xcf, 0x9d, 0xff, + 0x4f, 0x40, 0xa5, 0xb1, 0xe1, 0x44, 0xc9, 0xf8, 0x08, 0x9b, 0x34, 0xca, 0xa7, 0x33, 0x43, 0x1b, + 0x31, 0x87, 0xa1, 0xc7, 0xa0, 0x1c, 0x91, 0x75, 0x16, 0xb7, 0x69, 0x44, 0xf4, 0x60, 0xb2, 0x8e, + 0x69, 0xbb, 0xfd, 0x8b, 0xa5, 0xb4, 0xb9, 0x94, 0x7e, 0x6f, 0x3e, 0xdb, 0x1b, 0x9d, 0x28, 0x96, + 0x7e, 0x1f, 0x63, 0xb6, 0xb3, 0x66, 0x2c, 0xe1, 0xe8, 0x93, 0x16, 0x0c, 0xdd, 0x8a, 0x03, 0xdf, + 0x27, 0x89, 0x50, 0x4d, 0x37, 0x0a, 0x1e, 0x8a, 0x2b, 0x9c, 0xba, 0xee, 0x83, 0x68, 0xc0, 0x92, + 0x2f, 0xed, 0x2e, 0xd9, 0x6e, 0x78, 0x9d, 0x66, 0x57, 0x90, 0xc6, 0x45, 0xde, 0x8c, 0x25, 0x9c, + 0xa2, 0xba, 0x3e, 0x47, 0x1d, 0x48, 0xa3, 0xce, 0xfb, 0x02, 0x55, 0xc0, 0xed, 0xbf, 0x39, 0x08, + 0x67, 0x73, 0x17, 0x07, 0x35, 0x64, 0x98, 0xa9, 0x70, 0xc9, 0xf5, 0x88, 0x0c, 0x4f, 0x62, 0x86, + 0xcc, 0x0d, 0xd5, 0x8a, 0x0d, 0x0c, 0xf4, 0x93, 0x00, 0xa1, 0x13, 0x39, 0x6d, 0xa2, 0xfc, 0xb2, + 0x47, 0xb6, 0x17, 0x68, 0x3f, 0x96, 0x25, 0x4d, 0xbd, 0x37, 0x55, 0x4d, 0x31, 0x36, 0x58, 0xa2, + 0xe7, 0x61, 0x38, 0x22, 0x1e, 0x71, 0x62, 0x16, 0xf6, 0x9b, 0xcd, 0x61, 0xc0, 0x1a, 0x84, 0x4d, + 0x3c, 0xf4, 0xa4, 0x8a, 0xe4, 0xca, 0x44, 0xb4, 0xa4, 0xa3, 0xb9, 0xd0, 0x1b, 0x16, 0x8c, 0xad, + 0xbb, 0x1e, 0xd1, 0xdc, 0x45, 0xc6, 0xc1, 0xd2, 0xd1, 0x5f, 0xf2, 0x92, 0x49, 0x57, 0x4b, 0xc8, + 0x54, 0x73, 0x8c, 0x33, 0xec, 0xe9, 0x67, 0xde, 0x22, 0x11, 0x13, 0xad, 0x83, 0xe9, 0xcf, 0x7c, + 0x83, 0x37, 0x63, 0x09, 0x47, 0xd3, 0x70, 0x22, 0x74, 0xe2, 0x78, 0x26, 0x22, 0x4d, 0xe2, 0x27, + 0xae, 0xe3, 0xf1, 0x7c, 0x80, 0xaa, 0x8e, 0x07, 0x5e, 0x4e, 0x83, 0x71, 0x16, 0x1f, 0x7d, 0x10, + 0x1e, 0xe6, 0x8e, 0x8f, 0x45, 0x37, 0x8e, 0x5d, 0xbf, 0xa5, 0xa7, 0x81, 0xf0, 0xff, 0x4c, 0x08, + 0x52, 0x0f, 0xcf, 0xe7, 0xa3, 0xe1, 0x5e, 0xcf, 0xa3, 0xa7, 0xa1, 0x1a, 0x6f, 0xba, 0xe1, 0x4c, + 0xd4, 0x8c, 0xd9, 0xa1, 0x47, 0x55, 0x7b, 0x1b, 0x57, 0x44, 0x3b, 0x56, 0x18, 0xa8, 0x01, 0x23, + 0xfc, 0x93, 0xf0, 0x50, 0x34, 0x21, 0x1f, 0x9f, 0xe9, 0xa9, 0x1e, 0x45, 0x7a, 0xdb, 0x24, 0x76, + 0x6e, 0x5f, 0x94, 0x47, 0x30, 0xfc, 0xc4, 0xe0, 0x86, 0x41, 0x06, 0xa7, 0x88, 0xda, 0x3f, 0x5f, + 0x4a, 0xef, 0xb8, 0xcd, 0x45, 0x8a, 0x62, 0xba, 0x14, 0x93, 0x1b, 0x4e, 0x24, 0xbd, 0x31, 0x47, + 0x4c, 0x5b, 0x10, 0x74, 0x6f, 0x38, 0x91, 0xb9, 0xa8, 0x19, 0x03, 0x2c, 0x39, 0xa1, 0x5b, 0x30, + 0x90, 0x78, 0x4e, 0x41, 0x79, 0x4e, 0x06, 0x47, 0xed, 0x00, 0x59, 0x98, 0x8e, 0x31, 0xe3, 0x81, + 0x1e, 0xa5, 0x56, 0xff, 0x9a, 0x3c, 0x22, 0x11, 0x86, 0xfa, 0x5a, 0x8c, 0x59, 0xab, 0xfd, 0x2b, + 0x90, 0x23, 0x57, 0x95, 0x22, 0x43, 0x17, 0x00, 0xe8, 0x06, 0x72, 0x39, 0x22, 0xeb, 0xee, 0xb6, + 0x30, 0x24, 0xd4, 0xda, 0xbd, 0xa6, 0x20, 0xd8, 0xc0, 0x92, 0xcf, 0xac, 0x74, 0xd6, 0xe9, 0x33, + 0xa5, 0xee, 0x67, 0x38, 0x04, 0x1b, 0x58, 0xe8, 0x39, 0x18, 0x74, 0xdb, 0x4e, 0x4b, 0x85, 0x60, + 0x3e, 0x4a, 0x17, 0xed, 0x3c, 0x6b, 0xb9, 0xbb, 0x3b, 0x31, 0xa6, 0x3a, 0xc4, 0x9a, 0xb0, 0xc0, + 0x45, 0xbf, 0x6c, 0xc1, 0x48, 0x23, 0x68, 0xb7, 0x03, 0x9f, 0x6f, 0xbb, 0xc4, 0x1e, 0xf2, 0xd6, + 0x71, 0xa9, 0xf9, 0xc9, 0x19, 0x83, 0x19, 0xdf, 0x44, 0xaa, 0x84, 0x2c, 0x13, 0x84, 0x53, 0xbd, + 0x32, 0xd7, 0x76, 0x65, 0x9f, 0xb5, 0xfd, 0xeb, 0x16, 0x9c, 0xe2, 0xcf, 0x1a, 0xbb, 0x41, 0x91, + 0x7b, 0x14, 0x1c, 0xf3, 0x6b, 0x75, 0x6d, 0x90, 0x95, 0x97, 0xae, 0x0b, 0x8e, 0xbb, 0x3b, 0x89, + 0xe6, 0xe0, 0xd4, 0x7a, 0x10, 0x35, 0x88, 0x39, 0x10, 0x42, 0x30, 0x29, 0x42, 0x97, 0xb2, 0x08, + 0xb8, 0xfb, 0x19, 0x74, 0x03, 0x1e, 0x32, 0x1a, 0xcd, 0x71, 0xe0, 0xb2, 0xe9, 0x71, 0x41, 0xed, + 0xa1, 0x4b, 0xb9, 0x58, 0xb8, 0xc7, 0xd3, 0x69, 0x87, 0x49, 0xad, 0x0f, 0x87, 0xc9, 0xab, 0xf0, + 0x48, 0xa3, 0x7b, 0x64, 0xb6, 0xe2, 0xce, 0x5a, 0xcc, 0x25, 0x55, 0xb5, 0xfe, 0x03, 0x82, 0xc0, + 0x23, 0x33, 0xbd, 0x10, 0x71, 0x6f, 0x1a, 0xe8, 0xa3, 0x50, 0x8d, 0x08, 0xfb, 0x2a, 0xb1, 0x48, + 0xc4, 0x39, 0xe2, 0x2e, 0x59, 0x5b, 0xa0, 0x9c, 0xac, 0x96, 0xbd, 0xa2, 0x21, 0xc6, 0x8a, 0x23, + 0xba, 0x0d, 0x43, 0xa1, 0x93, 0x34, 0x36, 0x44, 0xfa, 0xcd, 0x91, 0xe3, 0x5f, 0x14, 0x73, 0xe6, + 0x03, 0x37, 0x12, 0x76, 0x39, 0x13, 0x2c, 0xb9, 0x51, 0x6b, 0xa4, 0x11, 0xb4, 0xc3, 0xc0, 0x27, + 0x7e, 0x12, 0x8f, 0x8f, 0x6a, 0x6b, 0x64, 0x46, 0xb5, 0x62, 0x03, 0xe3, 0xdc, 0xfb, 0xe1, 0x54, + 0xd7, 0xc2, 0x3b, 0x90, 0x73, 0x65, 0x16, 0x1e, 0xca, 0x9f, 0xe2, 0x07, 0x72, 0xb1, 0xfc, 0x93, + 0x4c, 0x90, 0xab, 0x61, 0xf6, 0xf6, 0xe1, 0xae, 0x73, 0xa0, 0x4c, 0xfc, 0x2d, 0x21, 0xf1, 0x2f, + 0x1d, 0x6d, 0xa4, 0x2f, 0xfa, 0x5b, 0x7c, 0x85, 0x32, 0x9f, 0xc4, 0x45, 0x7f, 0x0b, 0x53, 0xda, + 0xe8, 0x4b, 0x56, 0xca, 0x6c, 0xe3, 0x4e, 0xbe, 0x0f, 0x1f, 0x8b, 0x9d, 0xdf, 0xb7, 0x25, 0x67, + 0xff, 0xbb, 0x12, 0x9c, 0xdf, 0x8f, 0x48, 0x1f, 0xc3, 0xf7, 0x04, 0x0c, 0xc6, 0xec, 0xd8, 0x5a, + 0x88, 0xd0, 0x61, 0x3a, 0xb3, 0xf8, 0x41, 0xf6, 0xab, 0x58, 0x80, 0x90, 0x07, 0xe5, 0xb6, 0x13, + 0x0a, 0xdf, 0xcf, 0xfc, 0x51, 0xd3, 0x5e, 0xe8, 0x7f, 0xc7, 0x5b, 0x74, 0x42, 0xee, 0x51, 0x30, + 0x1a, 0x30, 0x65, 0x83, 0x12, 0xa8, 0x38, 0x51, 0xe4, 0xc8, 0x33, 0xd2, 0xab, 0xc5, 0xf0, 0x9b, + 0xa6, 0x24, 0xf9, 0x11, 0x53, 0xaa, 0x09, 0x73, 0x66, 0xf6, 0xe7, 0x86, 0x52, 0xa9, 0x1f, 0xec, + 0xe0, 0x3b, 0x86, 0x41, 0xe1, 0xf2, 0xb1, 0x8a, 0xce, 0x36, 0xe2, 0xb9, 0x7b, 0x6c, 0x57, 0x27, + 0x32, 0xa0, 0x05, 0x2b, 0xf4, 0x59, 0x8b, 0xe5, 0x19, 0xcb, 0x74, 0x18, 0xb1, 0x97, 0x3a, 0x9e, + 0xb4, 0x67, 0x33, 0x7b, 0x59, 0x36, 0x62, 0x93, 0xbb, 0xa8, 0x17, 0xc0, 0x6c, 0xc8, 0xee, 0x7a, + 0x01, 0xcc, 0x26, 0x94, 0x70, 0xb4, 0x9d, 0x73, 0xc0, 0x5d, 0x40, 0xae, 0x6a, 0x1f, 0x47, 0xda, + 0x5f, 0xb3, 0xe0, 0x94, 0x9b, 0x3d, 0xa9, 0x14, 0x3b, 0x8f, 0x23, 0x86, 0x50, 0xf4, 0x3e, 0x08, + 0x55, 0xca, 0xb7, 0x0b, 0x84, 0xbb, 0x3b, 0x83, 0x9a, 0x30, 0xe0, 0xfa, 0xeb, 0x81, 0x30, 0x39, + 0xea, 0x47, 0xeb, 0xd4, 0xbc, 0xbf, 0x1e, 0xe8, 0xd5, 0x4c, 0xff, 0x61, 0x46, 0x1d, 0x2d, 0xc0, + 0x99, 0x48, 0xf8, 0x86, 0x2e, 0xbb, 0x31, 0xdd, 0xc1, 0x2f, 0xb8, 0x6d, 0x37, 0x61, 0xe6, 0x42, + 0xb9, 0x3e, 0x7e, 0x67, 0x77, 0xe2, 0x0c, 0xce, 0x81, 0xe3, 0xdc, 0xa7, 0xd0, 0xeb, 0x30, 0x24, + 0x13, 0xa3, 0xab, 0x45, 0xec, 0xe2, 0xba, 0xe7, 0xbf, 0x9a, 0x4c, 0x2b, 0x22, 0x07, 0x5a, 0x32, + 0xb4, 0xdf, 0x18, 0x86, 0xee, 0x43, 0x4c, 0xf4, 0x31, 0xa8, 0x45, 0x2a, 0x59, 0xdb, 0x2a, 0x42, + 0xb9, 0xca, 0xef, 0x2b, 0x0e, 0x50, 0x95, 0xe1, 0xa2, 0xd3, 0xb2, 0x35, 0x47, 0xba, 0xbd, 0x88, + 0xf5, 0x59, 0x67, 0x01, 0x73, 0x5b, 0x70, 0xd5, 0xe7, 0x58, 0x3b, 0x7e, 0x03, 0x33, 0x1e, 0x28, + 0x82, 0xc1, 0x0d, 0xe2, 0x78, 0xc9, 0x46, 0x31, 0x2e, 0xf7, 0xcb, 0x8c, 0x56, 0x36, 0x65, 0x87, + 0xb7, 0x62, 0xc1, 0x09, 0x6d, 0xc3, 0xd0, 0x06, 0x9f, 0x00, 0xc2, 0xe2, 0x5f, 0x3c, 0xea, 0xe0, + 0xa6, 0x66, 0x95, 0xfe, 0xdc, 0xa2, 0x01, 0x4b, 0x76, 0x2c, 0x3a, 0xc6, 0x38, 0xbf, 0xe7, 0x4b, + 0xb7, 0xb8, 0x6c, 0xa5, 0xfe, 0x0f, 0xef, 0x3f, 0x02, 0x23, 0x11, 0x69, 0x04, 0x7e, 0xc3, 0xf5, + 0x48, 0x73, 0x5a, 0xba, 0xd3, 0x0f, 0x92, 0xe3, 0xc2, 0x76, 0xcd, 0xd8, 0xa0, 0x81, 0x53, 0x14, + 0xd1, 0x67, 0x2c, 0x18, 0x53, 0x19, 0x9e, 0xf4, 0x83, 0x10, 0xe1, 0xbe, 0x5d, 0x28, 0x28, 0x9f, + 0x94, 0xd1, 0xac, 0xa3, 0x3b, 0xbb, 0x13, 0x63, 0xe9, 0x36, 0x9c, 0xe1, 0x8b, 0x5e, 0x06, 0x08, + 0xd6, 0x78, 0x08, 0xcc, 0x74, 0x22, 0x7c, 0xb9, 0x07, 0x79, 0xd5, 0x31, 0x9e, 0xec, 0x26, 0x29, + 0x60, 0x83, 0x1a, 0xba, 0x0a, 0xc0, 0x97, 0xcd, 0xea, 0x4e, 0x28, 0xb7, 0x05, 0x32, 0x49, 0x09, + 0x56, 0x14, 0xe4, 0xee, 0xee, 0x44, 0xb7, 0x6f, 0x8d, 0x85, 0x19, 0x18, 0x8f, 0xa3, 0x9f, 0x80, + 0xa1, 0xb8, 0xd3, 0x6e, 0x3b, 0xca, 0xd3, 0x5b, 0x60, 0xfa, 0x1c, 0xa7, 0x6b, 0x88, 0x22, 0xde, + 0x80, 0x25, 0x47, 0x74, 0x8b, 0x0a, 0xd5, 0x58, 0x38, 0xfd, 0xd8, 0x2a, 0xe2, 0x36, 0xc1, 0x30, + 0x7b, 0xa7, 0xf7, 0xc8, 0x88, 0x1e, 0x9c, 0x83, 0x73, 0x77, 0x77, 0xe2, 0xa1, 0x74, 0xfb, 0x42, + 0x20, 0x12, 0xda, 0x72, 0x69, 0xa2, 0x2b, 0xb2, 0x4e, 0x0a, 0x7d, 0x6d, 0x99, 0xbe, 0xff, 0x94, + 0xae, 0x93, 0xc2, 0x9a, 0x7b, 0x8f, 0x99, 0xf9, 0x30, 0x5a, 0x84, 0xd3, 0x8d, 0xc0, 0x4f, 0xa2, + 0xc0, 0xf3, 0x78, 0x9d, 0x20, 0xbe, 0x43, 0xe3, 0x9e, 0xe0, 0xb7, 0x8b, 0x6e, 0x9f, 0x9e, 0xe9, + 0x46, 0xc1, 0x79, 0xcf, 0xd9, 0x7e, 0x3a, 0x36, 0x50, 0x0c, 0xce, 0x73, 0x30, 0x42, 0xb6, 0x13, + 0x12, 0xf9, 0x8e, 0x77, 0x1d, 0x2f, 0x48, 0x1f, 0x28, 0x5b, 0x03, 0x17, 0x8d, 0x76, 0x9c, 0xc2, + 0x42, 0xb6, 0x72, 0x4b, 0x18, 0x49, 0x9a, 0xdc, 0x2d, 0x21, 0x9d, 0x10, 0xf6, 0xff, 0x29, 0xa5, + 0x0c, 0xb2, 0xd5, 0x88, 0x10, 0x14, 0x40, 0xc5, 0x0f, 0x9a, 0x4a, 0xf6, 0x5f, 0x29, 0x46, 0xf6, + 0x5f, 0x0b, 0x9a, 0x46, 0x31, 0x15, 0xfa, 0x2f, 0xc6, 0x9c, 0x0f, 0xab, 0x36, 0x21, 0xcb, 0x72, + 0x30, 0x80, 0xd8, 0x68, 0x14, 0xc9, 0x59, 0x55, 0x9b, 0x58, 0x32, 0x19, 0xe1, 0x34, 0x5f, 0xb4, + 0x09, 0x95, 0x8d, 0x20, 0x4e, 0xe4, 0xf6, 0xe3, 0x88, 0x3b, 0x9d, 0xcb, 0x41, 0x9c, 0x30, 0x2b, + 0x42, 0xbd, 0x36, 0x6d, 0x89, 0x31, 0xe7, 0x61, 0xff, 0x57, 0x2b, 0xe5, 0xf1, 0xbe, 0xc9, 0xe2, + 0x64, 0xb7, 0x88, 0x4f, 0x97, 0xb5, 0x19, 0x18, 0xf4, 0xc3, 0x99, 0xac, 0xc3, 0x77, 0xf4, 0x2a, + 0x83, 0x75, 0x9b, 0x52, 0x98, 0x64, 0x24, 0x8c, 0x18, 0xa2, 0x4f, 0x58, 0xe9, 0xfc, 0xcf, 0x52, + 0x11, 0x1b, 0x0c, 0x33, 0x07, 0x7a, 0xdf, 0x54, 0x52, 0xfb, 0x4b, 0x16, 0x0c, 0xd5, 0x9d, 0xc6, + 0x66, 0xb0, 0xbe, 0x8e, 0x9e, 0x86, 0x6a, 0xb3, 0x13, 0x99, 0xa9, 0xa8, 0x6a, 0x9b, 0x3f, 0x2b, + 0xda, 0xb1, 0xc2, 0xa0, 0x73, 0x78, 0xdd, 0x69, 0xc8, 0x4c, 0xe8, 0x32, 0x9f, 0xc3, 0x97, 0x58, + 0x0b, 0x16, 0x10, 0xf4, 0x3c, 0x0c, 0xb7, 0x9d, 0x6d, 0xf9, 0x70, 0xd6, 0xdd, 0xbe, 0xa8, 0x41, + 0xd8, 0xc4, 0xb3, 0xff, 0x95, 0x05, 0xe3, 0x75, 0x27, 0x76, 0x1b, 0xd3, 0x9d, 0x64, 0xa3, 0xee, + 0x26, 0x6b, 0x9d, 0xc6, 0x26, 0x49, 0x78, 0xfa, 0x3b, 0xed, 0x65, 0x27, 0xa6, 0x4b, 0x49, 0xed, + 0xeb, 0x54, 0x2f, 0xaf, 0x8b, 0x76, 0xac, 0x30, 0xd0, 0xeb, 0x30, 0x1c, 0x3a, 0x71, 0x7c, 0x3b, + 0x88, 0x9a, 0x98, 0xac, 0x17, 0x53, 0x7c, 0x62, 0x85, 0x34, 0x22, 0x92, 0x60, 0xb2, 0x2e, 0x8e, + 0x84, 0x35, 0x7d, 0x6c, 0x32, 0xb3, 0xbf, 0x60, 0xc1, 0x23, 0x75, 0xe2, 0x44, 0x24, 0x62, 0xb5, + 0x2a, 0xd4, 0x8b, 0xcc, 0x78, 0x41, 0xa7, 0x89, 0x5e, 0x83, 0x6a, 0x42, 0x9b, 0x69, 0xb7, 0xac, + 0x62, 0xbb, 0xc5, 0x4e, 0x74, 0x57, 0x05, 0x71, 0xac, 0xd8, 0xd8, 0x7f, 0xcb, 0x82, 0x11, 0x76, + 0x38, 0x36, 0x4b, 0x12, 0xc7, 0xf5, 0xba, 0x4a, 0x3a, 0x59, 0x7d, 0x96, 0x74, 0x3a, 0x0f, 0x03, + 0x1b, 0x41, 0x9b, 0x64, 0x0f, 0x76, 0x2f, 0x07, 0x74, 0x5b, 0x4d, 0x21, 0xe8, 0x59, 0xfa, 0xe1, + 0x5d, 0x3f, 0x71, 0xe8, 0x12, 0x90, 0xce, 0xd7, 0x13, 0xfc, 0xa3, 0xab, 0x66, 0x6c, 0xe2, 0xd8, + 0xbf, 0x55, 0x83, 0x21, 0x71, 0xfa, 0xdf, 0x77, 0x09, 0x04, 0xb9, 0xbf, 0x2f, 0xf5, 0xdc, 0xdf, + 0xc7, 0x30, 0xd8, 0x60, 0xb5, 0xe5, 0x84, 0x19, 0x79, 0xb5, 0x90, 0x70, 0x11, 0x5e, 0xae, 0x4e, + 0x77, 0x8b, 0xff, 0xc7, 0x82, 0x15, 0xfa, 0xa2, 0x05, 0x27, 0x1a, 0x81, 0xef, 0x93, 0x86, 0xb6, + 0x71, 0x06, 0x8a, 0x88, 0x0a, 0x98, 0x49, 0x13, 0xd5, 0x27, 0x33, 0x19, 0x00, 0xce, 0xb2, 0x47, + 0x2f, 0xc2, 0x28, 0x1f, 0xb3, 0x1b, 0x29, 0x8f, 0xb1, 0xae, 0xf4, 0x63, 0x02, 0x71, 0x1a, 0x17, + 0x4d, 0x72, 0xcf, 0xbb, 0xa8, 0xa9, 0x33, 0xa8, 0x1d, 0x6b, 0x46, 0x35, 0x1d, 0x03, 0x03, 0x45, + 0x80, 0x22, 0xb2, 0x1e, 0x91, 0x78, 0x43, 0x44, 0x47, 0x30, 0xfb, 0x6a, 0xe8, 0x70, 0xe9, 0xd2, + 0xb8, 0x8b, 0x12, 0xce, 0xa1, 0x8e, 0x36, 0xc5, 0x06, 0xb3, 0x5a, 0x84, 0x0c, 0x15, 0x9f, 0xb9, + 0xe7, 0x3e, 0x73, 0x02, 0x2a, 0xf1, 0x86, 0x13, 0x35, 0x99, 0x5d, 0x57, 0xe6, 0x29, 0x3a, 0x2b, + 0xb4, 0x01, 0xf3, 0x76, 0x34, 0x0b, 0x27, 0x33, 0x75, 0x8a, 0x62, 0xe1, 0xd9, 0x55, 0xe9, 0x18, + 0x99, 0x0a, 0x47, 0x31, 0xee, 0x7a, 0xc2, 0x74, 0x3e, 0x0c, 0xef, 0xe3, 0x7c, 0xd8, 0x51, 0x31, + 0x78, 0xdc, 0xe7, 0xfa, 0x52, 0x21, 0x03, 0xd0, 0x57, 0xc0, 0xdd, 0xe7, 0x33, 0x01, 0x77, 0xa3, + 0xac, 0x03, 0x37, 0x8a, 0xe9, 0xc0, 0xc1, 0xa3, 0xeb, 0xee, 0x67, 0xb4, 0xdc, 0x9f, 0x5b, 0x20, + 0xbf, 0xeb, 0x8c, 0xd3, 0xd8, 0x20, 0x74, 0xca, 0xa0, 0xf7, 0xc1, 0x98, 0xda, 0x42, 0xcf, 0x04, + 0x1d, 0x9f, 0x07, 0xca, 0x95, 0xf5, 0x11, 0x2e, 0x4e, 0x41, 0x71, 0x06, 0x1b, 0x4d, 0x41, 0x8d, + 0x8e, 0x13, 0x7f, 0x94, 0xeb, 0x5a, 0xb5, 0x4d, 0x9f, 0x5e, 0x9e, 0x17, 0x4f, 0x69, 0x1c, 0x14, + 0xc0, 0x29, 0xcf, 0x89, 0x13, 0xd6, 0x03, 0xba, 0xa3, 0x3e, 0x64, 0xb1, 0x02, 0x16, 0xf3, 0xbf, + 0x90, 0x25, 0x84, 0xbb, 0x69, 0xdb, 0xdf, 0x1e, 0x80, 0xd1, 0x94, 0x64, 0x3c, 0xa0, 0x92, 0x7e, + 0x1a, 0xaa, 0x52, 0x6f, 0x66, 0xcb, 0xaa, 0x28, 0xe5, 0xaa, 0x30, 0xa8, 0xd2, 0x5a, 0xd3, 0x5a, + 0x35, 0x6b, 0x54, 0x18, 0x0a, 0x17, 0x9b, 0x78, 0x4c, 0x28, 0x27, 0x5e, 0x3c, 0xe3, 0xb9, 0xc4, + 0x4f, 0x78, 0x37, 0x8b, 0x11, 0xca, 0xab, 0x0b, 0x2b, 0x26, 0x51, 0x2d, 0x94, 0x33, 0x00, 0x9c, + 0x65, 0x8f, 0x3e, 0x6d, 0xc1, 0xa8, 0x73, 0x3b, 0xd6, 0x05, 0x50, 0x45, 0x68, 0xdd, 0x11, 0x95, + 0x54, 0xaa, 0xa6, 0x2a, 0x77, 0xf9, 0xa6, 0x9a, 0x70, 0x9a, 0x29, 0x7a, 0xd3, 0x02, 0x44, 0xb6, + 0x49, 0x43, 0x06, 0xff, 0x89, 0xbe, 0x0c, 0x16, 0xb1, 0xd3, 0xbc, 0xd8, 0x45, 0x97, 0x4b, 0xf5, + 0xee, 0x76, 0x9c, 0xd3, 0x07, 0xfb, 0x9f, 0x97, 0xd5, 0x82, 0xd2, 0xf1, 0xa6, 0x8e, 0x11, 0xf7, + 0x66, 0x1d, 0x3e, 0xee, 0x4d, 0xc7, 0x0f, 0x74, 0xe7, 0x40, 0xa6, 0x52, 0xa6, 0x4a, 0xf7, 0x29, + 0x65, 0xea, 0xa7, 0xac, 0x54, 0x01, 0xa1, 0xe1, 0x0b, 0x2f, 0x17, 0x1b, 0xeb, 0x3a, 0xc9, 0x63, + 0x1b, 0x32, 0xd2, 0x3d, 0x1d, 0xd2, 0x42, 0xa5, 0xa9, 0x81, 0x76, 0x20, 0x69, 0xf8, 0x1f, 0xca, + 0x30, 0x6c, 0x68, 0xd2, 0x5c, 0xb3, 0xc8, 0x7a, 0xc0, 0xcc, 0xa2, 0xd2, 0x01, 0xcc, 0xa2, 0x9f, + 0x84, 0x5a, 0x43, 0x4a, 0xf9, 0x62, 0x4a, 0xe8, 0x66, 0x75, 0x87, 0x16, 0xf4, 0xaa, 0x09, 0x6b, + 0x9e, 0x68, 0x2e, 0x95, 0x68, 0x23, 0x34, 0xc4, 0x00, 0xd3, 0x10, 0x79, 0x99, 0x30, 0x42, 0x53, + 0x74, 0x3f, 0xc3, 0xea, 0x4c, 0x85, 0xae, 0x78, 0x2f, 0x19, 0x91, 0xce, 0xeb, 0x4c, 0x2d, 0xcf, + 0xcb, 0x66, 0x6c, 0xe2, 0xd8, 0xdf, 0xb6, 0xd4, 0xc7, 0xbd, 0x07, 0x15, 0x15, 0x6e, 0xa5, 0x2b, + 0x2a, 0x5c, 0x2c, 0x64, 0x98, 0x7b, 0x94, 0x52, 0xb8, 0x06, 0x43, 0x33, 0x41, 0xbb, 0xed, 0xf8, + 0x4d, 0xf4, 0x83, 0x30, 0xd4, 0xe0, 0x3f, 0x85, 0x63, 0x87, 0x1d, 0x0f, 0x0a, 0x28, 0x96, 0x30, + 0xf4, 0x28, 0x0c, 0x38, 0x51, 0x4b, 0x3a, 0x73, 0x58, 0x28, 0xcc, 0x74, 0xd4, 0x8a, 0x31, 0x6b, + 0xb5, 0xff, 0xf1, 0x00, 0xb0, 0x13, 0x68, 0x27, 0x22, 0xcd, 0xd5, 0x80, 0x95, 0xf0, 0x3b, 0xd6, + 0x43, 0x35, 0xbd, 0x59, 0x7a, 0x90, 0x0f, 0xd6, 0x8c, 0xc3, 0x95, 0xf2, 0x3d, 0x3e, 0x5c, 0xe9, + 0x71, 0x5e, 0x36, 0xf0, 0x00, 0x9d, 0x97, 0xd9, 0x9f, 0xb3, 0x00, 0xa9, 0xb0, 0x05, 0x7d, 0xa0, + 0x3d, 0x05, 0x35, 0x15, 0xc0, 0x20, 0x0c, 0x2b, 0x2d, 0x22, 0x24, 0x00, 0x6b, 0x9c, 0x3e, 0x76, + 0xc8, 0x4f, 0x48, 0xf9, 0x5d, 0x4e, 0x47, 0xd1, 0x32, 0xa9, 0x2f, 0xc4, 0xb9, 0xfd, 0xdb, 0x25, + 0x78, 0x88, 0xab, 0xe4, 0x45, 0xc7, 0x77, 0x5a, 0xa4, 0x4d, 0x7b, 0xd5, 0x6f, 0x88, 0x42, 0x83, + 0x6e, 0xcd, 0x5c, 0x19, 0x15, 0x7b, 0xd4, 0xb5, 0xcb, 0xd7, 0x1c, 0x5f, 0x65, 0xf3, 0xbe, 0x9b, + 0x60, 0x46, 0x1c, 0xc5, 0x50, 0x95, 0xf5, 0xe5, 0x85, 0x2c, 0x2e, 0x88, 0x91, 0x12, 0x4b, 0x42, + 0x6f, 0x12, 0xac, 0x18, 0x51, 0xc3, 0xd5, 0x0b, 0x1a, 0x9b, 0x98, 0x84, 0x01, 0x93, 0xbb, 0x46, + 0x50, 0xe2, 0x82, 0x68, 0xc7, 0x0a, 0xc3, 0xfe, 0x6d, 0x0b, 0xb2, 0x1a, 0xc9, 0xa8, 0x95, 0x66, + 0xed, 0x59, 0x2b, 0xed, 0x00, 0xc5, 0xca, 0x7e, 0x1c, 0x86, 0x9d, 0x84, 0x1a, 0x11, 0x7c, 0xdb, + 0x5d, 0x3e, 0xdc, 0xb1, 0xc6, 0x62, 0xd0, 0x74, 0xd7, 0x5d, 0xb6, 0xdd, 0x36, 0xc9, 0xd9, 0xff, + 0x73, 0x00, 0x4e, 0x75, 0xe5, 0x6e, 0xa0, 0x17, 0x60, 0xa4, 0x21, 0xa6, 0x47, 0x28, 0x1d, 0x5a, + 0x35, 0x33, 0x88, 0x4d, 0xc3, 0x70, 0x0a, 0xb3, 0x8f, 0x09, 0x3a, 0x0f, 0xa7, 0x23, 0xba, 0xd1, + 0xef, 0x90, 0xe9, 0xf5, 0x84, 0x44, 0x2b, 0xa4, 0x11, 0xf8, 0x4d, 0x5e, 0xd1, 0xaf, 0x5c, 0x7f, + 0xf8, 0xce, 0xee, 0xc4, 0x69, 0xdc, 0x0d, 0xc6, 0x79, 0xcf, 0xa0, 0x10, 0x46, 0x3d, 0xd3, 0x06, + 0x14, 0x1b, 0x80, 0x43, 0x99, 0x8f, 0xca, 0x46, 0x48, 0x35, 0xe3, 0x34, 0x83, 0xb4, 0x21, 0x59, + 0xb9, 0x4f, 0x86, 0xe4, 0xa7, 0xb4, 0x21, 0xc9, 0xcf, 0xdf, 0x3f, 0x54, 0x70, 0xee, 0xce, 0x71, + 0x5b, 0x92, 0x2f, 0x41, 0x55, 0xc6, 0x26, 0xf5, 0x15, 0xd3, 0x63, 0xd2, 0xe9, 0x21, 0xd1, 0xee, + 0x96, 0x20, 0x67, 0x13, 0x42, 0xd7, 0x99, 0xd6, 0xf8, 0xa9, 0x75, 0x76, 0x30, 0xad, 0x8f, 0xb6, + 0x79, 0x5c, 0x16, 0xd7, 0x6d, 0x1f, 0x2c, 0x7a, 0x13, 0xa5, 0x43, 0xb5, 0x54, 0x4a, 0x83, 0x0a, + 0xd7, 0xba, 0x00, 0xa0, 0x0d, 0x35, 0x11, 0xb0, 0xae, 0x8e, 0x7d, 0xb5, 0x3d, 0x87, 0x0d, 0x2c, + 0xba, 0xa7, 0x76, 0xfd, 0x38, 0x71, 0x3c, 0xef, 0xb2, 0xeb, 0x27, 0xc2, 0x39, 0xa8, 0x94, 0xf8, + 0xbc, 0x06, 0x61, 0x13, 0xef, 0xdc, 0x7b, 0x8c, 0xef, 0x72, 0x90, 0xef, 0xb9, 0x01, 0x8f, 0xcc, + 0xb9, 0x89, 0x4a, 0xb3, 0x50, 0xf3, 0x88, 0xda, 0x61, 0x2a, 0x6d, 0xc8, 0xea, 0x99, 0x36, 0x64, + 0xa4, 0x39, 0x94, 0xd2, 0x59, 0x19, 0xd9, 0x34, 0x07, 0xfb, 0x05, 0x38, 0x33, 0xe7, 0x26, 0x97, + 0x5c, 0x8f, 0x1c, 0x90, 0x89, 0xfd, 0x9b, 0x83, 0x30, 0x62, 0x26, 0xea, 0x1d, 0x24, 0xf3, 0xe9, + 0x0b, 0xd4, 0xd4, 0x12, 0x6f, 0xe7, 0xaa, 0x43, 0xb3, 0x9b, 0x47, 0xce, 0x1a, 0xcc, 0x1f, 0x31, + 0xc3, 0xda, 0xd2, 0x3c, 0xb1, 0xd9, 0x01, 0x74, 0x1b, 0x2a, 0xeb, 0x2c, 0x0c, 0xbf, 0x5c, 0x44, + 0x64, 0x41, 0xde, 0x88, 0xea, 0x65, 0xc6, 0x03, 0xf9, 0x39, 0x3f, 0xaa, 0x21, 0xa3, 0x74, 0x6e, + 0x97, 0x11, 0x3a, 0x2a, 0xb2, 0xba, 0x14, 0x46, 0x2f, 0x51, 0x5f, 0x39, 0x84, 0xa8, 0x4f, 0x09, + 0xde, 0xc1, 0xfb, 0x24, 0x78, 0x59, 0x4a, 0x45, 0xb2, 0xc1, 0xec, 0x37, 0x11, 0xeb, 0x3e, 0xc4, + 0x06, 0xc1, 0x48, 0xa9, 0x48, 0x81, 0x71, 0x16, 0x1f, 0x7d, 0x5c, 0x89, 0xee, 0x6a, 0x11, 0x7e, + 0x55, 0x73, 0x46, 0x1f, 0xb7, 0xd4, 0xfe, 0x5c, 0x09, 0xc6, 0xe6, 0xfc, 0xce, 0xf2, 0xdc, 0x72, + 0x67, 0xcd, 0x73, 0x1b, 0x57, 0xc9, 0x0e, 0x15, 0xcd, 0x9b, 0x64, 0x67, 0x7e, 0x56, 0xac, 0x20, + 0x35, 0x67, 0xae, 0xd2, 0x46, 0xcc, 0x61, 0x54, 0x18, 0xad, 0xbb, 0x7e, 0x8b, 0x44, 0x61, 0xe4, + 0x0a, 0x97, 0xa7, 0x21, 0x8c, 0x2e, 0x69, 0x10, 0x36, 0xf1, 0x28, 0xed, 0xe0, 0xb6, 0x4f, 0xa2, + 0xac, 0x21, 0xbb, 0x44, 0x1b, 0x31, 0x87, 0x51, 0xa4, 0x24, 0xea, 0xc4, 0x89, 0x98, 0x8c, 0x0a, + 0x69, 0x95, 0x36, 0x62, 0x0e, 0xa3, 0x2b, 0x3d, 0xee, 0xac, 0xb1, 0xc0, 0x8d, 0x4c, 0x60, 0xfd, + 0x0a, 0x6f, 0xc6, 0x12, 0x4e, 0x51, 0x37, 0xc9, 0xce, 0x2c, 0xdd, 0xf5, 0x66, 0xf2, 0x6b, 0xae, + 0xf2, 0x66, 0x2c, 0xe1, 0xac, 0x14, 0x61, 0x7a, 0x38, 0xbe, 0xe7, 0x4a, 0x11, 0xa6, 0xbb, 0xdf, + 0x63, 0xff, 0xfc, 0x4b, 0x16, 0x8c, 0x98, 0xe1, 0x56, 0xa8, 0x95, 0xb1, 0x71, 0x97, 0xba, 0x2a, + 0xd9, 0xfe, 0x68, 0xde, 0x35, 0x60, 0x2d, 0x37, 0x09, 0xc2, 0xf8, 0x19, 0xe2, 0xb7, 0x5c, 0x9f, + 0xb0, 0x53, 0x74, 0x1e, 0xa6, 0x95, 0x8a, 0xe5, 0x9a, 0x09, 0x9a, 0xe4, 0x10, 0x46, 0xb2, 0x7d, + 0x13, 0x4e, 0x75, 0x25, 0x55, 0xf5, 0x61, 0x5a, 0xec, 0x9b, 0xd2, 0x6a, 0x63, 0x18, 0xa6, 0x84, + 0x65, 0x39, 0x9c, 0x19, 0x38, 0xc5, 0x17, 0x12, 0xe5, 0xb4, 0xd2, 0xd8, 0x20, 0x6d, 0x95, 0x28, + 0xc7, 0xfc, 0xeb, 0x37, 0xb2, 0x40, 0xdc, 0x8d, 0x6f, 0x7f, 0xde, 0x82, 0xd1, 0x54, 0x9e, 0x5b, + 0x41, 0x46, 0x10, 0x5b, 0x69, 0x01, 0x8b, 0xfe, 0x63, 0x21, 0xd0, 0x65, 0xa6, 0x4c, 0xf5, 0x4a, + 0xd3, 0x20, 0x6c, 0xe2, 0xd9, 0x5f, 0x2a, 0x41, 0x55, 0x46, 0x50, 0xf4, 0xd1, 0x95, 0xcf, 0x5a, + 0x30, 0xaa, 0xce, 0x34, 0x98, 0xb3, 0xac, 0x54, 0x44, 0x52, 0x02, 0xed, 0x81, 0xda, 0x6e, 0xfb, + 0xeb, 0x81, 0xb6, 0xc8, 0xb1, 0xc9, 0x0c, 0xa7, 0x79, 0xa3, 0x1b, 0x00, 0xf1, 0x4e, 0x9c, 0x90, + 0xb6, 0xe1, 0xb6, 0xb3, 0x8d, 0x15, 0x37, 0xd9, 0x08, 0x22, 0x42, 0xd7, 0xd7, 0xb5, 0xa0, 0x49, + 0x56, 0x14, 0xa6, 0x36, 0xa1, 0x74, 0x1b, 0x36, 0x28, 0xd9, 0xff, 0xb0, 0x04, 0x27, 0xb3, 0x5d, + 0x42, 0x1f, 0x82, 0x11, 0xc9, 0xdd, 0xb8, 0xd1, 0x4c, 0x86, 0x8d, 0x8c, 0x60, 0x03, 0x76, 0x77, + 0x77, 0x62, 0xa2, 0xfb, 0x4a, 0xb9, 0x49, 0x13, 0x05, 0xa7, 0x88, 0xf1, 0x83, 0x25, 0x71, 0x02, + 0x5a, 0xdf, 0x99, 0x0e, 0x43, 0x71, 0x3a, 0x64, 0x1c, 0x2c, 0x99, 0x50, 0x9c, 0xc1, 0x46, 0xcb, + 0x70, 0xc6, 0x68, 0xb9, 0x46, 0xdc, 0xd6, 0xc6, 0x5a, 0x10, 0xc9, 0x9d, 0xd5, 0xa3, 0x3a, 0xb0, + 0xab, 0x1b, 0x07, 0xe7, 0x3e, 0x49, 0xb5, 0x7d, 0xc3, 0x09, 0x9d, 0x86, 0x9b, 0xec, 0x08, 0x3f, + 0xa4, 0x92, 0x4d, 0x33, 0xa2, 0x1d, 0x2b, 0x0c, 0x7b, 0x11, 0x06, 0xfa, 0x9c, 0x41, 0x7d, 0x59, + 0xf4, 0x2f, 0x41, 0x95, 0x92, 0x93, 0xe6, 0x5d, 0x11, 0x24, 0x03, 0xa8, 0xca, 0x9b, 0x46, 0x90, + 0x0d, 0x65, 0xd7, 0x91, 0x67, 0x77, 0xea, 0xb5, 0xe6, 0xe3, 0xb8, 0xc3, 0x36, 0xc9, 0x14, 0x88, + 0x9e, 0x80, 0x32, 0xd9, 0x0e, 0xb3, 0x87, 0x74, 0x17, 0xb7, 0x43, 0x37, 0x22, 0x31, 0x45, 0x22, + 0xdb, 0x21, 0x3a, 0x07, 0x25, 0xb7, 0x29, 0x94, 0x14, 0x08, 0x9c, 0xd2, 0xfc, 0x2c, 0x2e, 0xb9, + 0x4d, 0x7b, 0x1b, 0x6a, 0xea, 0x6a, 0x13, 0xb4, 0x29, 0x65, 0xb7, 0x55, 0x44, 0xc8, 0x93, 0xa4, + 0xdb, 0x43, 0x6a, 0x77, 0x00, 0x74, 0xc2, 0x5f, 0x51, 0xf2, 0xe5, 0x3c, 0x0c, 0x34, 0x02, 0x91, + 0x8c, 0x5c, 0xd5, 0x64, 0x98, 0xd0, 0x66, 0x10, 0xfb, 0x26, 0x8c, 0x5d, 0xf5, 0x83, 0xdb, 0xac, + 0x2e, 0x3b, 0x2b, 0x43, 0x46, 0x09, 0xaf, 0xd3, 0x1f, 0x59, 0x13, 0x81, 0x41, 0x31, 0x87, 0xa9, + 0xfa, 0x4c, 0xa5, 0x5e, 0xf5, 0x99, 0xec, 0x4f, 0x58, 0x30, 0xa2, 0x32, 0x87, 0xe6, 0xb6, 0x36, + 0x29, 0xdd, 0x56, 0x14, 0x74, 0xc2, 0x2c, 0x5d, 0x76, 0xf9, 0x10, 0xe6, 0x30, 0x33, 0xa5, 0xae, + 0xb4, 0x4f, 0x4a, 0xdd, 0x79, 0x18, 0xd8, 0x74, 0xfd, 0x66, 0xf6, 0x36, 0x8d, 0xab, 0xae, 0xdf, + 0xc4, 0x0c, 0x42, 0xbb, 0x70, 0x52, 0x75, 0x41, 0x2a, 0x84, 0x17, 0x60, 0x64, 0xad, 0xe3, 0x7a, + 0x4d, 0x59, 0x5f, 0x2d, 0xe3, 0x29, 0xa9, 0x1b, 0x30, 0x9c, 0xc2, 0xa4, 0xfb, 0xba, 0x35, 0xd7, + 0x77, 0xa2, 0x9d, 0x65, 0xad, 0x81, 0x94, 0x50, 0xaa, 0x2b, 0x08, 0x36, 0xb0, 0xec, 0x37, 0xca, + 0x30, 0x96, 0xce, 0x9f, 0xea, 0x63, 0x7b, 0xf5, 0x04, 0x54, 0x58, 0x4a, 0x55, 0xf6, 0xd3, 0xf2, + 0x92, 0x64, 0x1c, 0x86, 0x62, 0x18, 0xe4, 0xc5, 0x18, 0x8a, 0xb9, 0x89, 0x46, 0x75, 0x52, 0xf9, + 0x57, 0x58, 0x3c, 0x99, 0xa8, 0xff, 0x20, 0x58, 0xa1, 0x4f, 0x5b, 0x30, 0x14, 0x84, 0x66, 0x5d, + 0x9f, 0x0f, 0x16, 0x99, 0x5b, 0x26, 0x92, 0x65, 0x84, 0x45, 0xac, 0x3e, 0xbd, 0xfc, 0x1c, 0x92, + 0xf5, 0xb9, 0xf7, 0xc2, 0x88, 0x89, 0xb9, 0x9f, 0x51, 0x5c, 0x35, 0x8d, 0xe2, 0xcf, 0x9a, 0x93, + 0x42, 0x64, 0xcf, 0xf5, 0xb1, 0xdc, 0xae, 0x43, 0xa5, 0xa1, 0x02, 0x00, 0x0e, 0x55, 0x95, 0x53, + 0x55, 0x47, 0x60, 0x87, 0x40, 0x9c, 0x9a, 0xfd, 0x6d, 0xcb, 0x98, 0x1f, 0x98, 0xc4, 0xf3, 0x4d, + 0x14, 0x41, 0xb9, 0xb5, 0xb5, 0x29, 0x4c, 0xd1, 0x2b, 0x05, 0x0d, 0xef, 0xdc, 0xd6, 0xa6, 0x9e, + 0xe3, 0x66, 0x2b, 0xa6, 0xcc, 0xfa, 0x70, 0x02, 0xa6, 0x92, 0x2c, 0xcb, 0xfb, 0x27, 0x59, 0xda, + 0x6f, 0x96, 0xe0, 0x54, 0xd7, 0xa4, 0x42, 0xaf, 0x43, 0x25, 0xa2, 0x6f, 0x29, 0x5e, 0x6f, 0xa1, + 0xb0, 0xb4, 0xc8, 0x78, 0xbe, 0xa9, 0xf5, 0x6e, 0xba, 0x1d, 0x73, 0x96, 0xe8, 0x0a, 0x20, 0x1d, + 0xa6, 0xa2, 0x3c, 0x90, 0xfc, 0x95, 0xcf, 0x89, 0x47, 0xd1, 0x74, 0x17, 0x06, 0xce, 0x79, 0x0a, + 0xbd, 0x98, 0x75, 0x64, 0x96, 0xd3, 0xe7, 0x96, 0x7b, 0xf9, 0x24, 0xed, 0x7f, 0x51, 0x82, 0xd1, + 0x54, 0x99, 0x25, 0xe4, 0x41, 0x95, 0x78, 0xcc, 0xa9, 0x2f, 0x95, 0xcd, 0x51, 0xab, 0x16, 0x2b, + 0x05, 0x79, 0x51, 0xd0, 0xc5, 0x8a, 0xc3, 0x83, 0x71, 0xb8, 0xfe, 0x02, 0x8c, 0xc8, 0x0e, 0x7d, + 0xd0, 0x69, 0x7b, 0x62, 0x00, 0xd5, 0x1c, 0xbd, 0x68, 0xc0, 0x70, 0x0a, 0xd3, 0xfe, 0x9d, 0x32, + 0x8c, 0xf3, 0x53, 0x90, 0xa6, 0x9a, 0x79, 0x8b, 0x72, 0xbf, 0xf5, 0xd7, 0x74, 0x31, 0x34, 0x3e, + 0x90, 0x6b, 0x47, 0xbd, 0x24, 0x20, 0x9f, 0x51, 0x5f, 0x91, 0x59, 0x5f, 0xcd, 0x44, 0x66, 0x71, + 0xb3, 0xbb, 0x75, 0x4c, 0x3d, 0xfa, 0xde, 0x0a, 0xd5, 0xfa, 0x95, 0x12, 0x9c, 0xc8, 0xdc, 0xc0, + 0x80, 0xde, 0x48, 0x17, 0xed, 0xb5, 0x8a, 0xf0, 0x95, 0xef, 0x59, 0x94, 0xff, 0x60, 0xa5, 0x7b, + 0xef, 0xd3, 0x52, 0xb1, 0xff, 0xa0, 0x04, 0x63, 0xe9, 0xab, 0x23, 0x1e, 0xc0, 0x91, 0x7a, 0x17, + 0xd4, 0x58, 0x75, 0x74, 0x76, 0x25, 0x26, 0x77, 0xc9, 0xf3, 0x42, 0xd4, 0xb2, 0x11, 0x6b, 0xf8, + 0x03, 0x51, 0x11, 0xd9, 0xfe, 0xfb, 0x16, 0x9c, 0xe5, 0x6f, 0x99, 0x9d, 0x87, 0x7f, 0x3d, 0x6f, + 0x74, 0x5f, 0x29, 0xb6, 0x83, 0x99, 0x22, 0x7e, 0xfb, 0x8d, 0x2f, 0xbb, 0x8a, 0x4f, 0xf4, 0x36, + 0x3d, 0x15, 0x1e, 0xc0, 0xce, 0x1e, 0x68, 0x32, 0xd8, 0x7f, 0x50, 0x06, 0x7d, 0xfb, 0x20, 0x72, + 0x45, 0x8e, 0x63, 0x21, 0xc5, 0x0c, 0x57, 0x76, 0xfc, 0x86, 0xbe, 0xe7, 0xb0, 0x9a, 0x49, 0x71, + 0xfc, 0x59, 0x0b, 0x86, 0x5d, 0xdf, 0x4d, 0x5c, 0x87, 0x6d, 0xa3, 0x8b, 0xb9, 0x19, 0x4d, 0xb1, + 0x9b, 0xe7, 0x94, 0x83, 0xc8, 0x3c, 0xc7, 0x51, 0xcc, 0xb0, 0xc9, 0x19, 0x7d, 0x44, 0x04, 0x4f, + 0x97, 0x0b, 0xcb, 0xce, 0xad, 0x66, 0x22, 0xa6, 0x43, 0x6a, 0x78, 0x25, 0x51, 0x41, 0x49, 0xed, + 0x98, 0x92, 0x52, 0x75, 0x71, 0xf5, 0x3d, 0xd0, 0xb4, 0x19, 0x73, 0x46, 0x76, 0x0c, 0xa8, 0x7b, + 0x2c, 0x0e, 0x18, 0x98, 0x3a, 0x05, 0x35, 0xa7, 0x93, 0x04, 0x6d, 0x3a, 0x4c, 0xe2, 0xa8, 0x49, + 0x87, 0xde, 0x4a, 0x00, 0xd6, 0x38, 0xf6, 0x1b, 0x15, 0xc8, 0x24, 0x1d, 0xa2, 0x6d, 0xf3, 0xe6, + 0x4c, 0xab, 0xd8, 0x9b, 0x33, 0x55, 0x67, 0xf2, 0x6e, 0xcf, 0x44, 0x2d, 0xa8, 0x84, 0x1b, 0x4e, + 0x2c, 0xcd, 0xea, 0x97, 0xd4, 0x3e, 0x8e, 0x36, 0xde, 0xdd, 0x9d, 0xf8, 0xb1, 0xfe, 0xbc, 0xae, + 0x74, 0xae, 0x4e, 0xf1, 0x62, 0x23, 0x9a, 0x35, 0xa3, 0x81, 0x39, 0xfd, 0x83, 0xdc, 0x0d, 0xf7, + 0x49, 0x51, 0x06, 0x1e, 0x93, 0xb8, 0xe3, 0x25, 0x62, 0x36, 0xbc, 0x54, 0xe0, 0x2a, 0xe3, 0x84, + 0x75, 0xba, 0x3c, 0xff, 0x8f, 0x0d, 0xa6, 0xe8, 0x43, 0x50, 0x8b, 0x13, 0x27, 0x4a, 0x0e, 0x99, + 0xe0, 0xaa, 0x06, 0x7d, 0x45, 0x12, 0xc1, 0x9a, 0x1e, 0x7a, 0x99, 0xd5, 0x76, 0x75, 0xe3, 0x8d, + 0x43, 0xe6, 0x3c, 0xc8, 0x3a, 0xb0, 0x82, 0x02, 0x36, 0xa8, 0xa1, 0x0b, 0x00, 0x6c, 0x6e, 0xf3, + 0x40, 0xbf, 0x2a, 0xf3, 0x32, 0x29, 0x51, 0x88, 0x15, 0x04, 0x1b, 0x58, 0xf6, 0x0f, 0x41, 0xba, + 0xde, 0x03, 0x9a, 0x90, 0xe5, 0x25, 0xb8, 0x17, 0x9a, 0xe5, 0x2e, 0xa4, 0x2a, 0x41, 0xfc, 0xba, + 0x05, 0x66, 0x51, 0x0a, 0xf4, 0x1a, 0xaf, 0x7e, 0x61, 0x15, 0x71, 0x72, 0x68, 0xd0, 0x9d, 0x5c, + 0x74, 0xc2, 0xcc, 0x11, 0xb6, 0x2c, 0x81, 0x71, 0xee, 0x3d, 0x50, 0x95, 0xd0, 0x03, 0x19, 0x75, + 0x1f, 0x87, 0xd3, 0xd9, 0x7b, 0xc5, 0xc5, 0xa9, 0xd3, 0xfe, 0xae, 0x1f, 0xe9, 0xcf, 0x29, 0xf5, + 0xf2, 0xe7, 0xf4, 0x71, 0x7f, 0xea, 0x6f, 0x58, 0x70, 0x7e, 0xbf, 0xeb, 0xcf, 0xd1, 0xa3, 0x30, + 0x70, 0xdb, 0x89, 0x64, 0xd1, 0x6d, 0x26, 0x28, 0x6f, 0x3a, 0x91, 0x8f, 0x59, 0x2b, 0xda, 0x81, + 0x41, 0x1e, 0x0d, 0x26, 0xac, 0xf5, 0x97, 0x8a, 0xbd, 0x8c, 0xfd, 0x2a, 0x31, 0xb6, 0x0b, 0x3c, + 0x12, 0x0d, 0x0b, 0x86, 0xf6, 0x77, 0x2c, 0x40, 0x4b, 0x5b, 0x24, 0x8a, 0xdc, 0xa6, 0x11, 0xbf, + 0xc6, 0xae, 0x53, 0x31, 0xae, 0x4d, 0x31, 0x53, 0x5c, 0x33, 0xd7, 0xa9, 0x18, 0xff, 0xf2, 0xaf, + 0x53, 0x29, 0x1d, 0xec, 0x3a, 0x15, 0xb4, 0x04, 0x67, 0xdb, 0x7c, 0xbb, 0xc1, 0xaf, 0x28, 0xe0, + 0x7b, 0x0f, 0x95, 0x50, 0xf6, 0xc8, 0x9d, 0xdd, 0x89, 0xb3, 0x8b, 0x79, 0x08, 0x38, 0xff, 0x39, + 0xfb, 0x3d, 0x80, 0x78, 0xd8, 0xda, 0x4c, 0x5e, 0x0c, 0x52, 0x4f, 0xf7, 0x8b, 0xfd, 0x95, 0x0a, + 0x9c, 0xc8, 0x94, 0x64, 0xa5, 0x5b, 0xbd, 0xee, 0xa0, 0xa7, 0x23, 0xeb, 0xef, 0xee, 0xee, 0xf5, + 0x15, 0x46, 0xe5, 0x43, 0xc5, 0xf5, 0xc3, 0x4e, 0x52, 0x4c, 0x0e, 0x29, 0xef, 0xc4, 0x3c, 0x25, + 0x68, 0xb8, 0x8b, 0xe9, 0x5f, 0xcc, 0xd9, 0x14, 0x19, 0x94, 0x95, 0x32, 0xc6, 0x07, 0xee, 0x93, + 0x3b, 0xe0, 0x93, 0x3a, 0x44, 0xaa, 0x52, 0x84, 0x63, 0x31, 0x33, 0x59, 0x8e, 0xfb, 0xa8, 0xfd, + 0xd7, 0x4a, 0x30, 0x6c, 0x7c, 0x34, 0xf4, 0x8b, 0xe9, 0x92, 0x4d, 0x56, 0x71, 0xaf, 0xc4, 0xe8, + 0x4f, 0xea, 0xa2, 0x4c, 0xfc, 0x95, 0x9e, 0xec, 0xae, 0xd6, 0x74, 0x77, 0x77, 0xe2, 0x64, 0xa6, + 0x1e, 0x53, 0xaa, 0x82, 0xd3, 0xb9, 0x8f, 0xc1, 0x89, 0x0c, 0x99, 0x9c, 0x57, 0x5e, 0x4d, 0x5f, + 0x1b, 0x7f, 0x44, 0xb7, 0x94, 0x39, 0x64, 0xdf, 0xa0, 0x43, 0x26, 0xd2, 0xe8, 0x02, 0x8f, 0xf4, + 0xe1, 0x83, 0xcd, 0x64, 0xcb, 0x96, 0xfa, 0xcc, 0x96, 0x7d, 0x0a, 0xaa, 0x61, 0xe0, 0xb9, 0x0d, + 0x57, 0x55, 0x21, 0x64, 0xf9, 0xb9, 0xcb, 0xa2, 0x0d, 0x2b, 0x28, 0xba, 0x0d, 0x35, 0x75, 0xc3, + 0xbe, 0xf0, 0x6f, 0x17, 0x75, 0xe8, 0xa3, 0x8c, 0x16, 0x7d, 0x73, 0xbe, 0xe6, 0x85, 0x6c, 0x18, + 0x64, 0x4a, 0x50, 0x86, 0xfe, 0x33, 0xdf, 0x3b, 0xd3, 0x8e, 0x31, 0x16, 0x10, 0xfb, 0xeb, 0x35, + 0x38, 0x93, 0x57, 0x17, 0x1b, 0x7d, 0x14, 0x06, 0x79, 0x1f, 0x8b, 0xb9, 0x7a, 0x21, 0x8f, 0xc7, + 0x1c, 0x23, 0x28, 0xba, 0xc5, 0x7e, 0x63, 0xc1, 0x53, 0x70, 0xf7, 0x9c, 0x35, 0x31, 0x43, 0x8e, + 0x87, 0xfb, 0x82, 0xa3, 0xb9, 0x2f, 0x38, 0x9c, 0xbb, 0xe7, 0xac, 0xa1, 0x6d, 0xa8, 0xb4, 0xdc, + 0x84, 0x38, 0xc2, 0x89, 0x70, 0xf3, 0x58, 0x98, 0x13, 0x87, 0x5b, 0x69, 0xec, 0x27, 0xe6, 0x0c, + 0xd1, 0xd7, 0x2c, 0x38, 0xb1, 0x96, 0x4e, 0x8d, 0x17, 0xc2, 0xd3, 0x39, 0x86, 0xda, 0xe7, 0x69, + 0x46, 0xfc, 0x3e, 0xa1, 0x4c, 0x23, 0xce, 0x76, 0x07, 0x7d, 0xca, 0x82, 0xa1, 0x75, 0xd7, 0x33, + 0xca, 0xe0, 0x1e, 0xc3, 0xc7, 0xb9, 0xc4, 0x18, 0xe8, 0x1d, 0x07, 0xff, 0x1f, 0x63, 0xc9, 0xb9, + 0x97, 0xa6, 0x1a, 0x3c, 0xaa, 0xa6, 0x1a, 0xba, 0x4f, 0x9a, 0xea, 0x33, 0x16, 0xd4, 0xd4, 0x48, + 0x8b, 0x74, 0xe7, 0x0f, 0x1d, 0xe3, 0x27, 0xe7, 0x9e, 0x13, 0xf5, 0x17, 0x6b, 0xe6, 0xe8, 0x8b, + 0x16, 0x0c, 0x3b, 0xaf, 0x77, 0x22, 0xd2, 0x24, 0x5b, 0x41, 0x18, 0x8b, 0xcb, 0x08, 0x5f, 0x29, + 0xbe, 0x33, 0xd3, 0x94, 0xc9, 0x2c, 0xd9, 0x5a, 0x0a, 0x63, 0x91, 0x96, 0xa4, 0x1b, 0xb0, 0xd9, + 0x05, 0x7b, 0xb7, 0x04, 0x13, 0xfb, 0x50, 0x40, 0x2f, 0xc0, 0x48, 0x10, 0xb5, 0x1c, 0xdf, 0x7d, + 0xdd, 0xac, 0x75, 0xa1, 0xac, 0xac, 0x25, 0x03, 0x86, 0x53, 0x98, 0x66, 0x42, 0x76, 0x69, 0x9f, + 0x84, 0xec, 0xf3, 0x30, 0x10, 0x91, 0x30, 0xc8, 0x6e, 0x16, 0x58, 0x4a, 0x00, 0x83, 0xa0, 0xc7, + 0xa0, 0xec, 0x84, 0xae, 0x08, 0x44, 0x53, 0x7b, 0xa0, 0xe9, 0xe5, 0x79, 0x4c, 0xdb, 0x53, 0xf5, + 0x21, 0x2a, 0xf7, 0xa4, 0x3e, 0x04, 0x55, 0x03, 0xe2, 0xec, 0x62, 0x50, 0xab, 0x81, 0xf4, 0x99, + 0x82, 0xfd, 0x66, 0x19, 0x1e, 0xdb, 0x73, 0xbe, 0xe8, 0x38, 0x3c, 0x6b, 0x8f, 0x38, 0x3c, 0x39, + 0x3c, 0xa5, 0xfd, 0x86, 0xa7, 0xdc, 0x63, 0x78, 0x3e, 0x45, 0x97, 0x81, 0xac, 0x11, 0x52, 0xcc, + 0x75, 0x72, 0xbd, 0x4a, 0x8e, 0x88, 0x15, 0x20, 0xa1, 0x58, 0xf3, 0xa5, 0x7b, 0x80, 0x54, 0x32, + 0x72, 0xa5, 0x08, 0x35, 0xd0, 0xb3, 0x66, 0x08, 0x9f, 0xfb, 0xbd, 0x32, 0x9c, 0xed, 0x9f, 0x2b, + 0xc1, 0x13, 0x7d, 0x48, 0x6f, 0x73, 0x16, 0x5b, 0x7d, 0xce, 0xe2, 0xef, 0xed, 0xcf, 0x64, 0xff, + 0x0d, 0x0b, 0xce, 0xf5, 0x56, 0x1e, 0xe8, 0x59, 0x18, 0x5e, 0x8b, 0x1c, 0xbf, 0xb1, 0xc1, 0xae, + 0xc8, 0x94, 0x83, 0xc2, 0xc6, 0x5a, 0x37, 0x63, 0x13, 0x87, 0x6e, 0x6f, 0x79, 0x4c, 0x82, 0x81, + 0x21, 0x93, 0x47, 0xe9, 0xf6, 0x76, 0x35, 0x0b, 0xc4, 0xdd, 0xf8, 0xf6, 0x9f, 0x95, 0xf2, 0xbb, + 0xc5, 0x8d, 0x8c, 0x83, 0x7c, 0x27, 0xf1, 0x15, 0x4a, 0x7d, 0xc8, 0x92, 0xf2, 0xbd, 0x96, 0x25, + 0x03, 0xbd, 0x64, 0x09, 0x9a, 0x85, 0x93, 0xc6, 0x15, 0x2a, 0x3c, 0x21, 0x98, 0x07, 0xdc, 0xaa, + 0x2a, 0x19, 0xcb, 0x19, 0x38, 0xee, 0x7a, 0x02, 0x3d, 0x0d, 0x55, 0xd7, 0x8f, 0x49, 0xa3, 0x13, + 0xf1, 0x40, 0x6f, 0x23, 0x09, 0x6b, 0x5e, 0xb4, 0x63, 0x85, 0x61, 0xff, 0x52, 0x09, 0x1e, 0xe9, + 0x69, 0x67, 0xdd, 0x23, 0xd9, 0x65, 0x7e, 0x8e, 0x81, 0x7b, 0xf3, 0x39, 0xcc, 0x41, 0xaa, 0xec, + 0x3b, 0x48, 0x7f, 0xd8, 0x7b, 0x62, 0x52, 0x9b, 0xfb, 0xfb, 0x76, 0x94, 0x5e, 0x84, 0x51, 0x27, + 0x0c, 0x39, 0x1e, 0x8b, 0xd7, 0xcc, 0x54, 0xc9, 0x99, 0x36, 0x81, 0x38, 0x8d, 0xdb, 0x97, 0xf6, + 0xfc, 0x63, 0x0b, 0x6a, 0x98, 0xac, 0x73, 0xe9, 0x80, 0x6e, 0x89, 0x21, 0xb2, 0x8a, 0xa8, 0xa7, + 0x49, 0x07, 0x36, 0x76, 0x59, 0x9d, 0xc9, 0xbc, 0xc1, 0xee, 0xbe, 0x6a, 0xa7, 0x74, 0xa0, 0xab, + 0x76, 0xd4, 0x65, 0x2b, 0xe5, 0xde, 0x97, 0xad, 0xd8, 0xdf, 0x18, 0xa2, 0xaf, 0x17, 0x06, 0x33, + 0x11, 0x69, 0xc6, 0xf4, 0xfb, 0x76, 0x22, 0x4f, 0x4c, 0x12, 0xf5, 0x7d, 0xaf, 0xe3, 0x05, 0x4c, + 0xdb, 0x53, 0x47, 0x31, 0xa5, 0x03, 0xd5, 0x08, 0x29, 0xef, 0x5b, 0x23, 0xe4, 0x45, 0x18, 0x8d, + 0xe3, 0x8d, 0xe5, 0xc8, 0xdd, 0x72, 0x12, 0x72, 0x95, 0xec, 0x08, 0x2b, 0x4b, 0xe7, 0xf5, 0xaf, + 0x5c, 0xd6, 0x40, 0x9c, 0xc6, 0x45, 0x73, 0x70, 0x4a, 0x57, 0xea, 0x20, 0x51, 0xc2, 0xa2, 0xfb, + 0xf9, 0x4c, 0x50, 0x49, 0xbc, 0xba, 0xb6, 0x87, 0x40, 0xc0, 0xdd, 0xcf, 0x50, 0xf9, 0x96, 0x6a, + 0xa4, 0x1d, 0x19, 0x4c, 0xcb, 0xb7, 0x14, 0x1d, 0xda, 0x97, 0xae, 0x27, 0xd0, 0x22, 0x9c, 0xe6, + 0x13, 0x63, 0x3a, 0x0c, 0x8d, 0x37, 0x1a, 0x4a, 0xd7, 0x31, 0x9c, 0xeb, 0x46, 0xc1, 0x79, 0xcf, + 0xa1, 0xe7, 0x61, 0x58, 0x35, 0xcf, 0xcf, 0x8a, 0x53, 0x04, 0xe5, 0xc5, 0x50, 0x64, 0xe6, 0x9b, + 0xd8, 0xc4, 0x43, 0x1f, 0x84, 0x87, 0xf5, 0x5f, 0x9e, 0x02, 0xc6, 0x8f, 0xd6, 0x66, 0x45, 0x11, + 0x24, 0x75, 0xb5, 0xc7, 0x5c, 0x2e, 0x5a, 0x13, 0xf7, 0x7a, 0x1e, 0xad, 0xc1, 0x39, 0x05, 0xba, + 0xe8, 0x27, 0x2c, 0x9f, 0x23, 0x26, 0x75, 0x27, 0x26, 0xd7, 0x23, 0x4f, 0xdc, 0x8d, 0xaa, 0x6e, + 0x5d, 0x9c, 0x73, 0x93, 0xcb, 0x79, 0x98, 0x78, 0x01, 0xef, 0x41, 0x05, 0x4d, 0x41, 0x8d, 0xf8, + 0xce, 0x9a, 0x47, 0x96, 0x66, 0xe6, 0x59, 0x31, 0x25, 0xe3, 0x24, 0xef, 0xa2, 0x04, 0x60, 0x8d, + 0xa3, 0x22, 0x4c, 0x47, 0x7a, 0xde, 0x00, 0xba, 0x0c, 0x67, 0x5a, 0x8d, 0x90, 0xda, 0x1e, 0x6e, + 0x83, 0x4c, 0x37, 0x58, 0x40, 0x1d, 0xfd, 0x30, 0xbc, 0xc0, 0xa4, 0x0a, 0x9f, 0x9e, 0x9b, 0x59, + 0xee, 0xc2, 0xc1, 0xb9, 0x4f, 0xb2, 0xc0, 0xcb, 0x28, 0xd8, 0xde, 0x19, 0x3f, 0x9d, 0x09, 0xbc, + 0xa4, 0x8d, 0x98, 0xc3, 0xd0, 0x15, 0x40, 0x2c, 0x16, 0xff, 0x72, 0x92, 0x84, 0xca, 0xd8, 0x19, + 0x3f, 0xc3, 0x5e, 0x49, 0x85, 0x91, 0x5d, 0xea, 0xc2, 0xc0, 0x39, 0x4f, 0xd9, 0xff, 0xd1, 0x82, + 0x51, 0xb5, 0x5e, 0xef, 0x41, 0x36, 0x8a, 0x97, 0xce, 0x46, 0x99, 0x3b, 0xba, 0xc4, 0x63, 0x3d, + 0xef, 0x11, 0xd2, 0xfc, 0xd3, 0xc3, 0x00, 0x5a, 0x2a, 0x2a, 0x85, 0x64, 0xf5, 0x54, 0x48, 0x0f, + 0xac, 0x44, 0xca, 0xab, 0x9c, 0x52, 0xb9, 0xbf, 0x95, 0x53, 0x56, 0xe0, 0xac, 0x34, 0x17, 0xf8, + 0x59, 0xd1, 0xe5, 0x20, 0x56, 0x02, 0xae, 0x5a, 0x7f, 0x4c, 0x10, 0x3a, 0x3b, 0x9f, 0x87, 0x84, + 0xf3, 0x9f, 0x4d, 0x59, 0x29, 0x43, 0xfb, 0x59, 0x29, 0x7a, 0x4d, 0x2f, 0xac, 0xcb, 0x3b, 0x3c, + 0x32, 0x6b, 0x7a, 0xe1, 0xd2, 0x0a, 0xd6, 0x38, 0xf9, 0x82, 0xbd, 0x56, 0x90, 0x60, 0x87, 0x03, + 0x0b, 0x76, 0x29, 0x62, 0x86, 0x7b, 0x8a, 0x18, 0xe9, 0x93, 0x1e, 0xe9, 0xe9, 0x93, 0x7e, 0x1f, + 0x8c, 0xb9, 0xfe, 0x06, 0x89, 0xdc, 0x84, 0x34, 0xd9, 0x5a, 0x60, 0xe2, 0xa7, 0xaa, 0xd5, 0xfa, + 0x7c, 0x0a, 0x8a, 0x33, 0xd8, 0x69, 0xb9, 0x38, 0xd6, 0x87, 0x5c, 0xec, 0xa1, 0x8d, 0x4e, 0x14, + 0xa3, 0x8d, 0x4e, 0x1e, 0x5d, 0x1b, 0x9d, 0x3a, 0x56, 0x6d, 0x84, 0x0a, 0xd1, 0x46, 0x7d, 0x09, + 0x7a, 0x63, 0xfb, 0x77, 0x66, 0x9f, 0xed, 0x5f, 0x2f, 0x55, 0x74, 0xf6, 0xd0, 0xaa, 0x28, 0x5f, + 0xcb, 0x3c, 0x74, 0x28, 0x2d, 0xf3, 0x99, 0x12, 0x9c, 0xd5, 0x72, 0x98, 0xce, 0x7e, 0x77, 0x9d, + 0x4a, 0x22, 0x76, 0x0d, 0x14, 0x3f, 0xb7, 0x31, 0x92, 0xa3, 0x74, 0x9e, 0x95, 0x82, 0x60, 0x03, + 0x8b, 0xe5, 0x18, 0x91, 0x88, 0x95, 0xd1, 0xcd, 0x0a, 0xe9, 0x19, 0xd1, 0x8e, 0x15, 0x06, 0x9d, + 0x5f, 0xf4, 0xb7, 0xc8, 0xdb, 0xcc, 0x16, 0x8b, 0x9b, 0xd1, 0x20, 0x6c, 0xe2, 0xa1, 0xa7, 0x38, + 0x13, 0x26, 0x20, 0xa8, 0xa0, 0x1e, 0x11, 0xf7, 0xc2, 0x4a, 0x99, 0xa0, 0xa0, 0xb2, 0x3b, 0x2c, + 0x99, 0xac, 0xd2, 0xdd, 0x1d, 0x16, 0x02, 0xa5, 0x30, 0xec, 0xff, 0x65, 0xc1, 0x23, 0xb9, 0x43, + 0x71, 0x0f, 0x94, 0xef, 0x76, 0x5a, 0xf9, 0xae, 0x14, 0xb5, 0xdd, 0x30, 0xde, 0xa2, 0x87, 0x22, + 0xfe, 0xf7, 0x16, 0x8c, 0x69, 0xfc, 0x7b, 0xf0, 0xaa, 0x6e, 0xfa, 0x55, 0x8b, 0xdb, 0x59, 0xd5, + 0xba, 0xde, 0xed, 0x77, 0x4a, 0xa0, 0x0a, 0x38, 0x4e, 0x37, 0x64, 0x79, 0xdc, 0x7d, 0x4e, 0x12, + 0x77, 0x60, 0x90, 0x1d, 0x84, 0xc6, 0xc5, 0x04, 0x79, 0xa4, 0xf9, 0xb3, 0x43, 0x55, 0x7d, 0xc8, + 0xcc, 0xfe, 0xc6, 0x58, 0x30, 0x64, 0x45, 0x9e, 0xdd, 0x98, 0x4a, 0xf3, 0xa6, 0x48, 0xcb, 0xd2, + 0x45, 0x9e, 0x45, 0x3b, 0x56, 0x18, 0x54, 0x3d, 0xb8, 0x8d, 0xc0, 0x9f, 0xf1, 0x9c, 0x58, 0xde, + 0x7d, 0xa8, 0xd4, 0xc3, 0xbc, 0x04, 0x60, 0x8d, 0xc3, 0xce, 0x48, 0xdd, 0x38, 0xf4, 0x9c, 0x1d, + 0x63, 0xff, 0x6c, 0xd4, 0x27, 0x50, 0x20, 0x6c, 0xe2, 0xd9, 0x6d, 0x18, 0x4f, 0xbf, 0xc4, 0x2c, + 0x59, 0x67, 0x01, 0x8a, 0x7d, 0x0d, 0xe7, 0x14, 0xd4, 0x1c, 0xf6, 0xd4, 0x42, 0xc7, 0xc9, 0x5e, + 0x59, 0x3e, 0x2d, 0x01, 0x58, 0xe3, 0xd8, 0xbf, 0x6a, 0xc1, 0xe9, 0x9c, 0x41, 0x2b, 0x30, 0xed, + 0x2d, 0xd1, 0xd2, 0x26, 0x4f, 0xb1, 0xbf, 0x13, 0x86, 0x9a, 0x64, 0xdd, 0x91, 0x21, 0x70, 0x86, + 0x6c, 0x9f, 0xe5, 0xcd, 0x58, 0xc2, 0xed, 0xff, 0x61, 0xc1, 0x89, 0x74, 0x5f, 0x63, 0x96, 0x4a, + 0xc2, 0x87, 0xc9, 0x8d, 0x1b, 0xc1, 0x16, 0x89, 0x76, 0xe8, 0x9b, 0x5b, 0x99, 0x54, 0x92, 0x2e, + 0x0c, 0x9c, 0xf3, 0x14, 0x2b, 0xdf, 0xda, 0x54, 0xa3, 0x2d, 0x67, 0xe4, 0x8d, 0x22, 0x67, 0xa4, + 0xfe, 0x98, 0xe6, 0x71, 0xb9, 0x62, 0x89, 0x4d, 0xfe, 0xf6, 0x77, 0x06, 0x40, 0xe5, 0xc5, 0xb2, + 0xf8, 0xa3, 0x82, 0xa2, 0xb7, 0x0e, 0x9a, 0x41, 0xa4, 0x26, 0xc3, 0xc0, 0x5e, 0x01, 0x01, 0xdc, + 0x4b, 0x62, 0xba, 0x2e, 0xd5, 0x1b, 0xae, 0x6a, 0x10, 0x36, 0xf1, 0x68, 0x4f, 0x3c, 0x77, 0x8b, + 0xf0, 0x87, 0x06, 0xd3, 0x3d, 0x59, 0x90, 0x00, 0xac, 0x71, 0x68, 0x4f, 0x9a, 0xee, 0xfa, 0xba, + 0xd8, 0xf2, 0xab, 0x9e, 0xd0, 0xd1, 0xc1, 0x0c, 0xc2, 0x2b, 0x72, 0x07, 0x9b, 0xc2, 0x0a, 0x36, + 0x2a, 0x72, 0x07, 0x9b, 0x98, 0x41, 0xa8, 0xdd, 0xe6, 0x07, 0x51, 0x9b, 0x5d, 0x29, 0xdf, 0x54, + 0x5c, 0x84, 0xf5, 0xab, 0xec, 0xb6, 0x6b, 0xdd, 0x28, 0x38, 0xef, 0x39, 0x3a, 0x03, 0xc3, 0x88, + 0x34, 0xdd, 0x46, 0x62, 0x52, 0x83, 0xf4, 0x0c, 0x5c, 0xee, 0xc2, 0xc0, 0x39, 0x4f, 0xa1, 0x69, + 0x38, 0x21, 0xf3, 0x9a, 0x65, 0xd5, 0x9a, 0xe1, 0x74, 0x95, 0x0c, 0x9c, 0x06, 0xe3, 0x2c, 0x3e, + 0x95, 0x6a, 0x6d, 0x51, 0xb0, 0x8a, 0x19, 0xcb, 0x86, 0x54, 0x93, 0x85, 0xac, 0xb0, 0xc2, 0xb0, + 0x3f, 0x59, 0xa6, 0x5a, 0xb8, 0x47, 0xa1, 0xb6, 0x7b, 0x16, 0x2d, 0x98, 0x9e, 0x91, 0x03, 0x7d, + 0xcc, 0xc8, 0xe7, 0x60, 0xe4, 0x56, 0x1c, 0xf8, 0x2a, 0x12, 0xaf, 0xd2, 0x33, 0x12, 0xcf, 0xc0, + 0xca, 0x8f, 0xc4, 0x1b, 0x2c, 0x2a, 0x12, 0x6f, 0xe8, 0x90, 0x91, 0x78, 0xdf, 0xaa, 0x80, 0xba, + 0x1a, 0xe4, 0x1a, 0x49, 0x6e, 0x07, 0xd1, 0xa6, 0xeb, 0xb7, 0x58, 0x3e, 0xf8, 0xd7, 0x2c, 0x18, + 0xe1, 0xeb, 0x65, 0xc1, 0xcc, 0xa4, 0x5a, 0x2f, 0xe8, 0xce, 0x89, 0x14, 0xb3, 0xc9, 0x55, 0x83, + 0x51, 0xe6, 0xea, 0x4d, 0x13, 0x84, 0x53, 0x3d, 0x42, 0x1f, 0x03, 0x90, 0xfe, 0xd1, 0x75, 0x29, + 0x32, 0xe7, 0x8b, 0xe9, 0x1f, 0x26, 0xeb, 0xda, 0x06, 0x5e, 0x55, 0x4c, 0xb0, 0xc1, 0x10, 0x7d, + 0x46, 0x67, 0x99, 0xf1, 0x90, 0xfd, 0x8f, 0x1c, 0xcb, 0xd8, 0xf4, 0x93, 0x63, 0x86, 0x61, 0xc8, + 0xf5, 0x5b, 0x74, 0x9e, 0x88, 0x88, 0xa5, 0x77, 0xe4, 0xd5, 0x52, 0x58, 0x08, 0x9c, 0x66, 0xdd, + 0xf1, 0x1c, 0xbf, 0x41, 0xa2, 0x79, 0x8e, 0x6e, 0x5e, 0x38, 0xcd, 0x1a, 0xb0, 0x24, 0xd4, 0x75, + 0xa9, 0x4a, 0xa5, 0x9f, 0x4b, 0x55, 0xce, 0xbd, 0x1f, 0x4e, 0x75, 0x7d, 0xcc, 0x03, 0xa5, 0x94, + 0x1d, 0x3e, 0x1b, 0xcd, 0xfe, 0x97, 0x83, 0x5a, 0x69, 0x5d, 0x0b, 0x9a, 0xfc, 0x6a, 0x8f, 0x48, + 0x7f, 0x51, 0x61, 0xe3, 0x16, 0x38, 0x45, 0x8c, 0x4b, 0xab, 0x55, 0x23, 0x36, 0x59, 0xd2, 0x39, + 0x1a, 0x3a, 0x11, 0xf1, 0x8f, 0x7b, 0x8e, 0x2e, 0x2b, 0x26, 0xd8, 0x60, 0x88, 0x36, 0x52, 0x39, + 0x25, 0x97, 0x8e, 0x9e, 0x53, 0xc2, 0xaa, 0x4c, 0xe5, 0x55, 0xe3, 0xff, 0xa2, 0x05, 0x63, 0x7e, + 0x6a, 0xe6, 0x16, 0x13, 0x46, 0x9a, 0xbf, 0x2a, 0xf8, 0xcd, 0x52, 0xe9, 0x36, 0x9c, 0xe1, 0x9f, + 0xa7, 0xd2, 0x2a, 0x07, 0x54, 0x69, 0xfa, 0x8e, 0xa0, 0xc1, 0x5e, 0x77, 0x04, 0x21, 0x5f, 0x5d, + 0x92, 0x36, 0x54, 0xf8, 0x25, 0x69, 0x90, 0x73, 0x41, 0xda, 0x4d, 0xa8, 0x35, 0x22, 0xe2, 0x24, + 0x87, 0xbc, 0x2f, 0x8b, 0x1d, 0xd0, 0xcf, 0x48, 0x02, 0x58, 0xd3, 0xb2, 0xff, 0xef, 0x00, 0x9c, + 0x94, 0x23, 0x22, 0x43, 0xd0, 0xa9, 0x7e, 0xe4, 0x7c, 0xb5, 0x71, 0xab, 0xf4, 0xe3, 0x65, 0x09, + 0xc0, 0x1a, 0x87, 0xda, 0x63, 0x9d, 0x98, 0x2c, 0x85, 0xc4, 0x5f, 0x70, 0xd7, 0x62, 0x71, 0xce, + 0xa9, 0x16, 0xca, 0x75, 0x0d, 0xc2, 0x26, 0x1e, 0x35, 0xc6, 0xb9, 0x5d, 0x1c, 0x67, 0xd3, 0x57, + 0x84, 0xbd, 0x8d, 0x25, 0x1c, 0xfd, 0x7c, 0x6e, 0xe5, 0xd8, 0x62, 0x12, 0xb7, 0xba, 0x22, 0xef, + 0x0f, 0x78, 0xc5, 0xe2, 0xdf, 0xb5, 0xe0, 0x2c, 0x6f, 0x95, 0x23, 0x79, 0x3d, 0x6c, 0x3a, 0x09, + 0x89, 0x8b, 0xa9, 0xe4, 0x9e, 0xd3, 0x3f, 0xed, 0xe4, 0xcd, 0x63, 0x8b, 0xf3, 0x7b, 0x83, 0xde, + 0xb0, 0xe0, 0xc4, 0x66, 0xaa, 0xe6, 0x87, 0x54, 0x1d, 0x47, 0x4d, 0xc7, 0x4f, 0x11, 0xd5, 0x4b, + 0x2d, 0xdd, 0x1e, 0xe3, 0x2c, 0x77, 0xfb, 0xcf, 0x2c, 0x30, 0xc5, 0xe8, 0xbd, 0x2f, 0x15, 0x72, + 0x70, 0x53, 0x50, 0x5a, 0x97, 0x95, 0x9e, 0xd6, 0xe5, 0x63, 0x50, 0xee, 0xb8, 0x4d, 0xb1, 0xbf, + 0xd0, 0xa7, 0xaf, 0xf3, 0xb3, 0x98, 0xb6, 0xdb, 0xff, 0xac, 0xa2, 0xfd, 0x16, 0x22, 0x2f, 0xea, + 0xfb, 0xe2, 0xb5, 0xd7, 0x55, 0xb1, 0x31, 0xfe, 0xe6, 0xd7, 0xba, 0x8a, 0x8d, 0xfd, 0xc8, 0xc1, + 0xd3, 0xde, 0xf8, 0x00, 0xf5, 0xaa, 0x35, 0x36, 0xb4, 0x4f, 0xce, 0xdb, 0x2d, 0xa8, 0xd2, 0x2d, + 0x18, 0x73, 0x40, 0x56, 0x53, 0x9d, 0xaa, 0x5e, 0x16, 0xed, 0x77, 0x77, 0x27, 0xde, 0x7b, 0xf0, + 0x6e, 0xc9, 0xa7, 0xb1, 0xa2, 0x8f, 0x62, 0xa8, 0xd1, 0xdf, 0x2c, 0x3d, 0x4f, 0x6c, 0xee, 0xae, + 0x2b, 0x99, 0x29, 0x01, 0x85, 0xe4, 0xfe, 0x69, 0x3e, 0xc8, 0x87, 0x1a, 0xbb, 0x8d, 0x96, 0x31, + 0xe5, 0x7b, 0xc0, 0x65, 0x95, 0x24, 0x27, 0x01, 0x77, 0x77, 0x27, 0x5e, 0x3c, 0x38, 0x53, 0xf5, + 0x38, 0xd6, 0x2c, 0xec, 0x2f, 0x0d, 0xe8, 0xb9, 0x2b, 0x6a, 0xcc, 0x7d, 0x5f, 0xcc, 0xdd, 0x17, + 0x32, 0x73, 0xf7, 0x7c, 0xd7, 0xdc, 0x1d, 0xd3, 0xb7, 0xa6, 0xa6, 0x66, 0xe3, 0xbd, 0x36, 0x04, + 0xf6, 0xf7, 0x37, 0x30, 0x0b, 0xe8, 0xb5, 0x8e, 0x1b, 0x91, 0x78, 0x39, 0xea, 0xf8, 0xae, 0xdf, + 0x62, 0xd3, 0xb1, 0x6a, 0x5a, 0x40, 0x29, 0x30, 0xce, 0xe2, 0xd3, 0x4d, 0x3d, 0xfd, 0xe6, 0x37, + 0x9d, 0x2d, 0x3e, 0xab, 0x8c, 0xb2, 0x5b, 0x2b, 0xa2, 0x1d, 0x2b, 0x0c, 0xfb, 0x1b, 0xec, 0x2c, + 0xdb, 0xc8, 0x0b, 0xa6, 0x73, 0xc2, 0x63, 0xd7, 0xff, 0xf2, 0x9a, 0x5d, 0x6a, 0x4e, 0xf0, 0x3b, + 0x7f, 0x39, 0x0c, 0xdd, 0x86, 0xa1, 0x35, 0x7e, 0xff, 0x5d, 0x31, 0xf5, 0xc9, 0xc5, 0x65, 0x7a, + 0xec, 0x96, 0x13, 0x79, 0xb3, 0xde, 0x5d, 0xfd, 0x13, 0x4b, 0x6e, 0xf6, 0xef, 0x57, 0xe0, 0x44, + 0xe6, 0x82, 0xd8, 0x54, 0xb5, 0xd4, 0xd2, 0xbe, 0xd5, 0x52, 0x3f, 0x0c, 0xd0, 0x24, 0xa1, 0x17, + 0xec, 0x30, 0x73, 0x6c, 0xe0, 0xc0, 0xe6, 0x98, 0xb2, 0xe0, 0x67, 0x15, 0x15, 0x6c, 0x50, 0x14, + 0x85, 0xca, 0x78, 0xf1, 0xd5, 0x4c, 0xa1, 0x32, 0xe3, 0x16, 0x83, 0xc1, 0x7b, 0x7b, 0x8b, 0x81, + 0x0b, 0x27, 0x78, 0x17, 0x55, 0xf6, 0xed, 0x21, 0x92, 0x6c, 0x59, 0xfe, 0xc2, 0x6c, 0x9a, 0x0c, + 0xce, 0xd2, 0xbd, 0x9f, 0xf7, 0x3f, 0xa3, 0x77, 0x41, 0x4d, 0x7e, 0xe7, 0x78, 0xbc, 0xa6, 0x2b, + 0x18, 0xc8, 0x69, 0xc0, 0xee, 0x65, 0x16, 0x3f, 0xbb, 0x0a, 0x09, 0xc0, 0xfd, 0x2a, 0x24, 0x60, + 0x7f, 0xa1, 0x44, 0xed, 0x78, 0xde, 0x2f, 0x55, 0x13, 0xe7, 0x49, 0x18, 0x74, 0x3a, 0xc9, 0x46, + 0xd0, 0x75, 0x9b, 0xdf, 0x34, 0x6b, 0xc5, 0x02, 0x8a, 0x16, 0x60, 0xa0, 0xa9, 0xeb, 0x9c, 0x1c, + 0xe4, 0x7b, 0x6a, 0x97, 0xa8, 0x93, 0x10, 0xcc, 0xa8, 0xa0, 0x47, 0x61, 0x20, 0x71, 0x5a, 0x32, + 0xe5, 0x8a, 0xa5, 0xd9, 0xae, 0x3a, 0xad, 0x18, 0xb3, 0x56, 0x53, 0x7d, 0x0f, 0xec, 0xa3, 0xbe, + 0x5f, 0x84, 0xd1, 0xd8, 0x6d, 0xf9, 0x4e, 0xd2, 0x89, 0x88, 0x71, 0xcc, 0xa7, 0x23, 0x37, 0x4c, + 0x20, 0x4e, 0xe3, 0xda, 0xbf, 0x39, 0x02, 0x67, 0x56, 0x66, 0x16, 0x65, 0xf5, 0xee, 0x63, 0xcb, + 0x9a, 0xca, 0xe3, 0x71, 0xef, 0xb2, 0xa6, 0x7a, 0x70, 0xf7, 0x8c, 0xac, 0x29, 0xcf, 0xc8, 0x9a, + 0x4a, 0xa7, 0xb0, 0x94, 0x8b, 0x48, 0x61, 0xc9, 0xeb, 0x41, 0x3f, 0x29, 0x2c, 0xc7, 0x96, 0x46, + 0xb5, 0x67, 0x87, 0x0e, 0x94, 0x46, 0xa5, 0x72, 0xcc, 0x0a, 0x49, 0x2e, 0xe8, 0xf1, 0xa9, 0x72, + 0x73, 0xcc, 0x54, 0x7e, 0x0f, 0x4f, 0x9c, 0x11, 0xa2, 0xfe, 0x95, 0xe2, 0x3b, 0xd0, 0x47, 0x7e, + 0x8f, 0xc8, 0xdd, 0x31, 0x73, 0xca, 0x86, 0x8a, 0xc8, 0x29, 0xcb, 0xeb, 0xce, 0xbe, 0x39, 0x65, + 0x2f, 0xc2, 0x68, 0xc3, 0x0b, 0x7c, 0xb2, 0x1c, 0x05, 0x49, 0xd0, 0x08, 0x3c, 0x61, 0xd6, 0x2b, + 0x91, 0x30, 0x63, 0x02, 0x71, 0x1a, 0xb7, 0x57, 0x42, 0x5a, 0xed, 0xa8, 0x09, 0x69, 0x70, 0x9f, + 0x12, 0xd2, 0x7e, 0x46, 0xa7, 0x4e, 0x0f, 0xb3, 0x2f, 0xf2, 0xe1, 0xe2, 0xbf, 0x48, 0x3f, 0xf9, + 0xd3, 0xe8, 0x4d, 0x7e, 0x9d, 0x1e, 0x35, 0x8c, 0x67, 0x82, 0x36, 0x35, 0xfc, 0x46, 0xd8, 0x90, + 0xbc, 0x7a, 0x0c, 0x13, 0xf6, 0xe6, 0x8a, 0x66, 0xa3, 0xae, 0xd8, 0xd3, 0x4d, 0x38, 0xdd, 0x91, + 0xa3, 0xa4, 0x76, 0x7f, 0xa5, 0x04, 0x3f, 0xb0, 0x6f, 0x17, 0xd0, 0x6d, 0x80, 0xc4, 0x69, 0x89, + 0x89, 0x2a, 0x0e, 0x4c, 0x8e, 0x18, 0x5e, 0xb9, 0x2a, 0xe9, 0xf1, 0x9a, 0x24, 0xea, 0x2f, 0x3b, + 0x8a, 0x90, 0xbf, 0x59, 0x54, 0x65, 0xe0, 0x75, 0x95, 0x6e, 0xc4, 0x81, 0x47, 0x30, 0x83, 0x50, + 0xf5, 0x1f, 0x91, 0x96, 0xbe, 0xff, 0x59, 0x7d, 0x3e, 0xcc, 0x5a, 0xb1, 0x80, 0xa2, 0xe7, 0x61, + 0xd8, 0xf1, 0x3c, 0x9e, 0x1f, 0x43, 0x62, 0x71, 0x9f, 0x8e, 0xae, 0x21, 0xa7, 0x41, 0xd8, 0xc4, + 0xb3, 0xff, 0xb4, 0x04, 0x13, 0xfb, 0xc8, 0x94, 0xae, 0x8c, 0xbf, 0x4a, 0xdf, 0x19, 0x7f, 0x22, + 0x47, 0x61, 0xb0, 0x47, 0x8e, 0xc2, 0xf3, 0x30, 0x9c, 0x10, 0xa7, 0x2d, 0x02, 0xb2, 0x84, 0x27, + 0x40, 0x9f, 0x00, 0x6b, 0x10, 0x36, 0xf1, 0xa8, 0x14, 0x1b, 0x73, 0x1a, 0x0d, 0x12, 0xc7, 0x32, + 0x09, 0x41, 0x78, 0x53, 0x0b, 0xcb, 0x70, 0x60, 0x4e, 0xea, 0xe9, 0x14, 0x0b, 0x9c, 0x61, 0x99, + 0x1d, 0xf0, 0x5a, 0x9f, 0x03, 0xfe, 0xf5, 0x12, 0x3c, 0xb6, 0xa7, 0x76, 0xeb, 0x3b, 0x3f, 0xa4, + 0x13, 0x93, 0x28, 0x3b, 0x71, 0xae, 0xc7, 0x24, 0xc2, 0x0c, 0xc2, 0x47, 0x29, 0x0c, 0x8d, 0xfb, + 0xb5, 0x8b, 0x4e, 0x5e, 0xe2, 0xa3, 0x94, 0x62, 0x81, 0x33, 0x2c, 0x0f, 0x3b, 0x2d, 0xff, 0x41, + 0x09, 0x9e, 0xe8, 0xc3, 0x06, 0x28, 0x30, 0xc9, 0x2b, 0x9d, 0x6a, 0x57, 0xbe, 0x4f, 0x19, 0x91, + 0x87, 0x1c, 0xae, 0x6f, 0x94, 0xe0, 0x5c, 0x6f, 0x55, 0x8c, 0x7e, 0x14, 0x4e, 0x44, 0x2a, 0x0a, + 0xcb, 0xcc, 0xd2, 0x3b, 0xcd, 0x3d, 0x09, 0x29, 0x10, 0xce, 0xe2, 0xa2, 0x49, 0x80, 0xd0, 0x49, + 0x36, 0xe2, 0x8b, 0xdb, 0x6e, 0x9c, 0x88, 0x2a, 0x34, 0x63, 0xfc, 0xec, 0x4a, 0xb6, 0x62, 0x03, + 0x83, 0xb2, 0x63, 0xff, 0x66, 0x83, 0x6b, 0x41, 0xc2, 0x1f, 0xe2, 0xdb, 0x88, 0xd3, 0xf2, 0xce, + 0x0e, 0x03, 0x84, 0xb3, 0xb8, 0x94, 0x1d, 0x3b, 0x1d, 0xe5, 0x1d, 0xe5, 0xfb, 0x0b, 0xc6, 0x6e, + 0x41, 0xb5, 0x62, 0x03, 0x23, 0x9b, 0x7f, 0x58, 0xd9, 0x3f, 0xff, 0xd0, 0xfe, 0xa7, 0x25, 0x78, + 0xa4, 0xa7, 0x29, 0xd7, 0xdf, 0x02, 0x7c, 0xf0, 0x72, 0x06, 0x0f, 0x37, 0x77, 0x0e, 0x98, 0xdb, + 0xf6, 0xc7, 0x3d, 0x66, 0x9a, 0xc8, 0x6d, 0x3b, 0x7c, 0x72, 0xf8, 0x83, 0x37, 0x9e, 0x5d, 0xe9, + 0x6c, 0x03, 0x07, 0x48, 0x67, 0xcb, 0x7c, 0x8c, 0x4a, 0x9f, 0x0b, 0xf9, 0xcf, 0xcb, 0x3d, 0x87, + 0x97, 0x6e, 0xfd, 0xfa, 0xf2, 0xd3, 0xce, 0xc2, 0x49, 0xd7, 0x67, 0xf7, 0x37, 0xad, 0x74, 0xd6, + 0x44, 0x61, 0x92, 0x52, 0xfa, 0xf6, 0xf4, 0xf9, 0x0c, 0x1c, 0x77, 0x3d, 0xf1, 0x00, 0xa6, 0x17, + 0x1e, 0x6e, 0x48, 0x0f, 0x96, 0xe0, 0x8a, 0x96, 0xe0, 0xac, 0x1c, 0x8a, 0x0d, 0x27, 0x22, 0x4d, + 0xa1, 0x46, 0x62, 0x91, 0x50, 0xf1, 0x08, 0x4f, 0xca, 0xc8, 0x41, 0xc0, 0xf9, 0xcf, 0xb1, 0x2b, + 0x73, 0x82, 0xd0, 0x6d, 0x88, 0x4d, 0x8e, 0xbe, 0x32, 0x87, 0x36, 0x62, 0x0e, 0xb3, 0x3f, 0x0c, + 0x35, 0xf5, 0xfe, 0x3c, 0xac, 0x5b, 0x4d, 0xba, 0xae, 0xb0, 0x6e, 0x35, 0xe3, 0x0c, 0x2c, 0xfa, + 0xb5, 0xa8, 0x49, 0x9c, 0x59, 0x3d, 0x57, 0xc9, 0x0e, 0xb3, 0x8f, 0xed, 0x77, 0xc3, 0x88, 0xf2, + 0xb3, 0xf4, 0x7b, 0x91, 0x90, 0xfd, 0xa5, 0x41, 0x18, 0x4d, 0x15, 0x07, 0x4c, 0x39, 0x58, 0xad, + 0x7d, 0x1d, 0xac, 0x2c, 0x4c, 0xbf, 0xe3, 0xcb, 0x5b, 0xc6, 0x8c, 0x30, 0xfd, 0x8e, 0x4f, 0x30, + 0x87, 0x51, 0xf3, 0xb6, 0x19, 0xed, 0xe0, 0x8e, 0x2f, 0xc2, 0x69, 0x95, 0x79, 0x3b, 0xcb, 0x5a, + 0xb1, 0x80, 0xa2, 0x4f, 0x58, 0x30, 0x12, 0x33, 0xef, 0x3d, 0x77, 0x4f, 0x8b, 0x49, 0x77, 0xe5, + 0xe8, 0xb5, 0x0f, 0x55, 0x21, 0x4c, 0x16, 0x21, 0x63, 0xb6, 0xe0, 0x14, 0x47, 0xf4, 0x69, 0x0b, + 0x6a, 0xea, 0x32, 0x14, 0x71, 0x15, 0xe0, 0x4a, 0xb1, 0xb5, 0x17, 0xb9, 0x5f, 0x53, 0x1d, 0x84, + 0xa8, 0x22, 0x78, 0x58, 0x33, 0x46, 0xb1, 0xf2, 0x1d, 0x0f, 0x1d, 0x8f, 0xef, 0x18, 0x72, 0xfc, + 0xc6, 0xef, 0x82, 0x5a, 0xdb, 0xf1, 0xdd, 0x75, 0x12, 0x27, 0xdc, 0x9d, 0x2b, 0x4b, 0xc2, 0xca, + 0x46, 0xac, 0xe1, 0x54, 0x21, 0xc7, 0xec, 0xc5, 0x12, 0xc3, 0xff, 0xca, 0x14, 0xf2, 0x8a, 0x6e, + 0xc6, 0x26, 0x8e, 0xe9, 0x2c, 0x86, 0xfb, 0xea, 0x2c, 0x1e, 0xde, 0xdb, 0x59, 0x6c, 0xff, 0x23, + 0x0b, 0xce, 0xe6, 0x7e, 0xb5, 0x07, 0x37, 0xf0, 0xd1, 0xfe, 0x72, 0x05, 0x4e, 0xe7, 0x54, 0xf9, + 0x44, 0x3b, 0xe6, 0x7c, 0xb6, 0x8a, 0x88, 0x21, 0x48, 0x1f, 0x89, 0xcb, 0x61, 0xcc, 0x99, 0xc4, + 0x07, 0x3b, 0xaa, 0xd1, 0xc7, 0x25, 0xe5, 0x7b, 0x7b, 0x5c, 0x62, 0x4c, 0xcb, 0x81, 0xfb, 0x3a, + 0x2d, 0x2b, 0xfb, 0x9c, 0x61, 0xfc, 0x9a, 0x05, 0xe3, 0xed, 0x1e, 0xa5, 0xe5, 0x85, 0xe3, 0xf1, + 0xc6, 0xf1, 0x14, 0xae, 0xaf, 0x3f, 0x7a, 0x67, 0x77, 0xa2, 0x67, 0x45, 0x7f, 0xdc, 0xb3, 0x57, + 0xf6, 0x77, 0xca, 0xc0, 0x4a, 0xcc, 0xb2, 0x4a, 0x6e, 0x3b, 0xe8, 0xe3, 0x66, 0xb1, 0x60, 0xab, + 0xa8, 0xc2, 0xb6, 0x9c, 0xb8, 0x2a, 0x36, 0xcc, 0x47, 0x30, 0xaf, 0xf6, 0x70, 0x56, 0x68, 0x95, + 0xfa, 0x10, 0x5a, 0x9e, 0xac, 0xca, 0x5c, 0x2e, 0xbe, 0x2a, 0x73, 0x2d, 0x5b, 0x91, 0x79, 0xef, + 0x4f, 0x3c, 0xf0, 0x40, 0x7e, 0xe2, 0x5f, 0xb0, 0xb8, 0xe0, 0xc9, 0x7c, 0x05, 0x6d, 0x19, 0x58, + 0x7b, 0x58, 0x06, 0x4f, 0x43, 0x35, 0x26, 0xde, 0xfa, 0x65, 0xe2, 0x78, 0xc2, 0x82, 0xd0, 0xe7, + 0xd7, 0xa2, 0x1d, 0x2b, 0x0c, 0x76, 0x6d, 0xab, 0xe7, 0x05, 0xb7, 0x2f, 0xb6, 0xc3, 0x64, 0x47, + 0xd8, 0x12, 0xfa, 0xda, 0x56, 0x05, 0xc1, 0x06, 0x96, 0xfd, 0x77, 0x4a, 0x7c, 0x06, 0x8a, 0x20, + 0x88, 0x17, 0x32, 0x17, 0xed, 0xf5, 0x1f, 0x3f, 0xf0, 0x51, 0x80, 0x86, 0xba, 0xa2, 0x5e, 0x9c, + 0x09, 0x5d, 0x3e, 0xf2, 0xfd, 0xd9, 0x82, 0x9e, 0x7e, 0x0d, 0xdd, 0x86, 0x0d, 0x7e, 0x29, 0x59, + 0x5a, 0xde, 0x57, 0x96, 0xa6, 0xc4, 0xca, 0xc0, 0x3e, 0xda, 0xee, 0x4f, 0x2d, 0x48, 0x59, 0x44, + 0x28, 0x84, 0x0a, 0xed, 0xee, 0x4e, 0x31, 0xb7, 0xef, 0x9b, 0xa4, 0xa9, 0x68, 0x14, 0xd3, 0x9e, + 0xfd, 0xc4, 0x9c, 0x11, 0xf2, 0x44, 0xac, 0x04, 0x1f, 0xd5, 0x6b, 0xc5, 0x31, 0xbc, 0x1c, 0x04, + 0x9b, 0xfc, 0x60, 0x53, 0xc7, 0x5d, 0xd8, 0x2f, 0xc0, 0xa9, 0xae, 0x4e, 0xb1, 0x3b, 0xb5, 0x02, + 0xaa, 0x7d, 0x32, 0xd3, 0x95, 0x25, 0x70, 0x62, 0x0e, 0xb3, 0xbf, 0x61, 0xc1, 0xc9, 0x2c, 0x79, + 0xf4, 0xa6, 0x05, 0xa7, 0xe2, 0x2c, 0xbd, 0xe3, 0x1a, 0x3b, 0x15, 0xef, 0xd8, 0x05, 0xc2, 0xdd, + 0x9d, 0xb0, 0xff, 0x9f, 0x98, 0xfc, 0x37, 0x5d, 0xbf, 0x19, 0xdc, 0x56, 0x86, 0x89, 0xd5, 0xd3, + 0x30, 0xa1, 0xeb, 0xb1, 0xb1, 0x41, 0x9a, 0x1d, 0xaf, 0x2b, 0x73, 0x74, 0x45, 0xb4, 0x63, 0x85, + 0xc1, 0x12, 0xe5, 0x3a, 0xa2, 0x6c, 0x7b, 0x66, 0x52, 0xce, 0x8a, 0x76, 0xac, 0x30, 0xd0, 0x73, + 0x30, 0x62, 0xbc, 0xa4, 0x9c, 0x97, 0xcc, 0x20, 0x37, 0x54, 0x66, 0x8c, 0x53, 0x58, 0x68, 0x12, + 0x40, 0x19, 0x39, 0x52, 0x45, 0x32, 0x47, 0x91, 0x92, 0x44, 0x31, 0x36, 0x30, 0x58, 0x5a, 0xaa, + 0xd7, 0x89, 0x99, 0x8f, 0x7f, 0x50, 0x97, 0x12, 0x9d, 0x11, 0x6d, 0x58, 0x41, 0xa9, 0x34, 0x69, + 0x3b, 0x7e, 0xc7, 0xf1, 0xe8, 0x08, 0x89, 0xad, 0x9f, 0x5a, 0x86, 0x8b, 0x0a, 0x82, 0x0d, 0x2c, + 0xfa, 0xc6, 0x89, 0xdb, 0x26, 0x2f, 0x07, 0xbe, 0x8c, 0x53, 0xd3, 0xc7, 0x3e, 0xa2, 0x1d, 0x2b, + 0x0c, 0xfb, 0xbf, 0x59, 0x70, 0x42, 0x27, 0xb9, 0xf3, 0xdb, 0xb3, 0xcd, 0x9d, 0xaa, 0xb5, 0xef, + 0x4e, 0x35, 0x9d, 0xfd, 0x5b, 0xea, 0x2b, 0xfb, 0xd7, 0x4c, 0xcc, 0x2d, 0xef, 0x99, 0x98, 0xfb, + 0x83, 0xfa, 0x66, 0x56, 0x9e, 0xc1, 0x3b, 0x9c, 0x77, 0x2b, 0x2b, 0xb2, 0x61, 0xb0, 0xe1, 0xa8, + 0x0a, 0x2f, 0x23, 0x7c, 0xef, 0x30, 0x33, 0xcd, 0x90, 0x04, 0xc4, 0x5e, 0x82, 0x9a, 0x3a, 0xfd, + 0x90, 0x1b, 0x55, 0x2b, 0x7f, 0xa3, 0xda, 0x57, 0x82, 0x60, 0x7d, 0xed, 0x9b, 0xdf, 0x7d, 0xfc, + 0x6d, 0xbf, 0xf7, 0xdd, 0xc7, 0xdf, 0xf6, 0x47, 0xdf, 0x7d, 0xfc, 0x6d, 0x9f, 0xb8, 0xf3, 0xb8, + 0xf5, 0xcd, 0x3b, 0x8f, 0x5b, 0xbf, 0x77, 0xe7, 0x71, 0xeb, 0x8f, 0xee, 0x3c, 0x6e, 0x7d, 0xe7, + 0xce, 0xe3, 0xd6, 0x17, 0xff, 0xf3, 0xe3, 0x6f, 0x7b, 0x39, 0x37, 0x50, 0x91, 0xfe, 0x78, 0xa6, + 0xd1, 0x9c, 0xda, 0xba, 0xc0, 0x62, 0xe5, 0xe8, 0xf2, 0x9a, 0x32, 0xe6, 0xd4, 0x94, 0x5c, 0x5e, + 0xff, 0x3f, 0x00, 0x00, 0xff, 0xff, 0xe2, 0x8b, 0xe4, 0x9e, 0x5b, 0xe1, 0x00, 0x00, } func (m *AWSAuthConfig) Marshal() (dAtA []byte, err error) { @@ -5158,6 +5159,11 @@ func (m *AWSAuthConfig) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + i -= len(m.Profile) + copy(dAtA[i:], m.Profile) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Profile))) + i-- + dAtA[i] = 0x1a i -= len(m.RoleARN) copy(dAtA[i:], m.RoleARN) i = encodeVarintGenerated(dAtA, i, uint64(len(m.RoleARN))) @@ -14357,6 +14363,8 @@ func (m *AWSAuthConfig) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) l = len(m.RoleARN) n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Profile) + n += 1 + l + sovGenerated(uint64(l)) return n } @@ -17806,6 +17814,7 @@ func (this *AWSAuthConfig) String() string { s := strings.Join([]string{`&AWSAuthConfig{`, `ClusterName:` + fmt.Sprintf("%v", this.ClusterName) + `,`, `RoleARN:` + fmt.Sprintf("%v", this.RoleARN) + `,`, + `Profile:` + fmt.Sprintf("%v", this.Profile) + `,`, `}`, }, "") return s @@ -20456,6 +20465,38 @@ func (m *AWSAuthConfig) Unmarshal(dAtA []byte) error { } m.RoleARN = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Profile", 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.Profile = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/pkg/apis/application/v1alpha1/generated.proto b/pkg/apis/application/v1alpha1/generated.proto index a2b17373de259..8a6fa85d9ad1b 100644 --- a/pkg/apis/application/v1alpha1/generated.proto +++ b/pkg/apis/application/v1alpha1/generated.proto @@ -22,6 +22,9 @@ message AWSAuthConfig { // RoleARN contains optional role ARN. If set then AWS IAM Authenticator assume a role to perform cluster operations instead of the default AWS credential provider chain. optional string roleARN = 2; + + // Profile contains optional role ARN. If set then AWS IAM Authenticator uses the profile to perform cluster operations instead of the default AWS credential provider chain. + optional string profile = 3; } // AppProject provides a logical grouping of applications, providing controls for: diff --git a/pkg/apis/application/v1alpha1/openapi_generated.go b/pkg/apis/application/v1alpha1/openapi_generated.go index 2274e252a4148..ae07404f60f2c 100644 --- a/pkg/apis/application/v1alpha1/openapi_generated.go +++ b/pkg/apis/application/v1alpha1/openapi_generated.go @@ -191,6 +191,13 @@ func schema_pkg_apis_application_v1alpha1_AWSAuthConfig(ref common.ReferenceCall Format: "", }, }, + "profile": { + SchemaProps: spec.SchemaProps{ + Description: "Profile contains optional role ARN. If set then AWS IAM Authenticator uses the profile to perform cluster operations instead of the default AWS credential provider chain.", + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, diff --git a/pkg/apis/application/v1alpha1/types.go b/pkg/apis/application/v1alpha1/types.go index e271003ad6ad0..18829dbcf940d 100644 --- a/pkg/apis/application/v1alpha1/types.go +++ b/pkg/apis/application/v1alpha1/types.go @@ -1856,6 +1856,9 @@ type AWSAuthConfig struct { // RoleARN contains optional role ARN. If set then AWS IAM Authenticator assume a role to perform cluster operations instead of the default AWS credential provider chain. RoleARN string `json:"roleARN,omitempty" protobuf:"bytes,2,opt,name=roleARN"` + + // Profile contains optional role ARN. If set then AWS IAM Authenticator uses the profile to perform cluster operations instead of the default AWS credential provider chain. + Profile string `json:"profile,omitempty" protobuf:"bytes,3,opt,name=profile"` } // ExecProviderConfig is config used to call an external command to perform cluster authentication @@ -2987,6 +2990,9 @@ func (c *Cluster) RawRestConfig() *rest.Config { if c.Config.AWSAuthConfig.RoleARN != "" { args = append(args, "--role-arn", c.Config.AWSAuthConfig.RoleARN) } + if c.Config.AWSAuthConfig.Profile != "" { + args = append(args, "--profile", c.Config.AWSAuthConfig.Profile) + } config = &rest.Config{ Host: c.Server, TLSClientConfig: tlsClientConfig, diff --git a/reposerver/gpgwatcher.go b/reposerver/gpgwatcher.go index 9c2c9be790813..5b43d6a24ac76 100644 --- a/reposerver/gpgwatcher.go +++ b/reposerver/gpgwatcher.go @@ -19,7 +19,7 @@ func StartGPGWatcher(sourcePath string) error { forceSync := false watcher, err := fsnotify.NewWatcher() if err != nil { - return err + return fmt.Errorf("failed to create fsnotify Watcher: %w", err) } defer func(watcher *fsnotify.Watcher) { if err = watcher.Close(); err != nil { @@ -83,7 +83,7 @@ func StartGPGWatcher(sourcePath string) error { err = watcher.Add(sourcePath) if err != nil { - return err + return fmt.Errorf("failed to add a new source to the watcher: %w", err) } <-done return fmt.Errorf("Abnormal termination of GPG watcher, refusing to continue.") diff --git a/reposerver/metrics/githandlers_test.go b/reposerver/metrics/githandlers_test.go new file mode 100644 index 0000000000000..6eaeeca82cc36 --- /dev/null +++ b/reposerver/metrics/githandlers_test.go @@ -0,0 +1,122 @@ +package metrics + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "golang.org/x/sync/semaphore" +) + +func TestMain(m *testing.M) { + os.Exit(m.Run()) +} + +func TestEdgeCasesAndErrorHandling(t *testing.T) { + tests := []struct { + name string + setup func() + teardown func() + testFunc func(t *testing.T) + }{ + { + name: "lsRemoteParallelismLimitSemaphore is nil", + testFunc: func(t *testing.T) { + lsRemoteParallelismLimitSemaphore = nil + assert.NotPanics(t, func() { + NewGitClientEventHandlers(&MetricsServer{}) + }) + }, + }, + { + name: "lsRemoteParallelismLimitSemaphore is not nil", + setup: func() { + lsRemoteParallelismLimitSemaphore = semaphore.NewWeighted(1) + }, + teardown: func() { + lsRemoteParallelismLimitSemaphore = nil + }, + testFunc: func(t *testing.T) { + assert.NotPanics(t, func() { + NewGitClientEventHandlers(&MetricsServer{}) + }) + }, + }, + { + name: "lsRemoteParallelismLimitSemaphore is not nil and Acquire returns error", + setup: func() { + lsRemoteParallelismLimitSemaphore = semaphore.NewWeighted(1) + }, + teardown: func() { + lsRemoteParallelismLimitSemaphore = nil + }, + testFunc: func(t *testing.T) { + assert.NotPanics(t, func() { + NewGitClientEventHandlers(&MetricsServer{}) + }) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + if tt.teardown != nil { + defer tt.teardown() + } + tt.testFunc(t) + }) + } +} + +func TestSemaphoreFunctionality(t *testing.T) { + os.Setenv("ARGOCD_GIT_LSREMOTE_PARALLELISM_LIMIT", "1") + + tests := []struct { + name string + setup func() + teardown func() + testFunc func(t *testing.T) + }{ + { + name: "lsRemoteParallelismLimitSemaphore is not nil", + setup: func() { + lsRemoteParallelismLimitSemaphore = semaphore.NewWeighted(1) + }, + teardown: func() { + lsRemoteParallelismLimitSemaphore = nil + }, + testFunc: func(t *testing.T) { + assert.NotPanics(t, func() { + NewGitClientEventHandlers(&MetricsServer{}) + }) + }, + }, + { + name: "lsRemoteParallelismLimitSemaphore is not nil and Acquire returns error", + setup: func() { + lsRemoteParallelismLimitSemaphore = semaphore.NewWeighted(1) + }, + teardown: func() { + lsRemoteParallelismLimitSemaphore = nil + }, + testFunc: func(t *testing.T) { + assert.NotPanics(t, func() { + NewGitClientEventHandlers(&MetricsServer{}) + }) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + if tt.teardown != nil { + defer tt.teardown() + } + tt.testFunc(t) + }) + } +} diff --git a/reposerver/server.go b/reposerver/server.go index 007b7136e41ed..e1d611801c3ec 100644 --- a/reposerver/server.go +++ b/reposerver/server.go @@ -102,7 +102,7 @@ func NewServer(metricsServer *metrics.MetricsServer, cache *reposervercache.Cach } repoService := repository.NewService(metricsServer, cache, initConstants, argo.NewResourceTracking(), gitCredsStore, filepath.Join(os.TempDir(), "_argocd-repo")) if err := repoService.Init(); err != nil { - return nil, err + return nil, fmt.Errorf("failed to initialize the repo service: %w", err) } return &ArgoCDRepoServer{ diff --git a/resource_customizations/beat.k8s.elastic.co/Beat/health.lua b/resource_customizations/beat.k8s.elastic.co/Beat/health.lua new file mode 100644 index 0000000000000..c7639dbbd94f0 --- /dev/null +++ b/resource_customizations/beat.k8s.elastic.co/Beat/health.lua @@ -0,0 +1,31 @@ +local hs = {} + +if obj.status ~= nil and (obj.status.health ~= nil or obj.status.expectedNodes ~= nil) then + if obj.status.health == "red" then + hs.status = "Degraded" + hs.message = "Elastic Beat status is Red" + return hs + elseif obj.status.health == "green" then + hs.status = "Healthy" + hs.message = "Elastic Beat status is Green" + return hs + elseif obj.status.health == "yellow" then + if obj.status.availableNodes ~= nil and obj.status.expectedNodes ~= nil then + hs.status = "Progressing" + hs.message = "Elastic Beat status is deploying, there is " .. obj.status.availableNodes .. " instance(s) on " .. obj.status.expectedNodes .. " expected" + return hs + else + hs.status = "Progressing" + hs.message = "Elastic Beat phase is progressing" + return hs + end + elseif obj.status.health == nil then + hs.status = "Progressing" + hs.message = "Elastic Beat phase is progressing" + return hs + end +end + +hs.status = "Unknown" +hs.message = "Elastic Beat status is unknown. Ensure your ArgoCD is current and then check for/file a bug report: https://github.com/argoproj/argo-cd/issues" +return hs diff --git a/resource_customizations/beat.k8s.elastic.co/Beat/health_test.yaml b/resource_customizations/beat.k8s.elastic.co/Beat/health_test.yaml new file mode 100644 index 0000000000000..fb44e998ffaf1 --- /dev/null +++ b/resource_customizations/beat.k8s.elastic.co/Beat/health_test.yaml @@ -0,0 +1,29 @@ +tests: +- healthStatus: + status: Healthy + message: "Elastic Beat status is Green" + inputPath: testdata/ready_green.yaml +- healthStatus: + status: Progressing + message: "Elastic Beat phase is progressing" + inputPath: testdata/ready_yellow_single_node.yaml +- healthStatus: + status: Progressing + message: "Elastic Beat status is deploying, there is 1 instance(s) on 2 expected" + inputPath: testdata/ready_yellow.yaml +- healthStatus: + status: Progressing + message: "Elastic Beat phase is progressing" + inputPath: testdata/progressing.yaml +- healthStatus: + status: Degraded + message: "Elastic Beat status is Red" + inputPath: testdata/ready_red.yaml +- healthStatus: + status: Unknown + message: "Elastic Beat status is unknown. Ensure your ArgoCD is current and then check for/file a bug report: https://github.com/argoproj/argo-cd/issues" + inputPath: testdata/unknown.yaml +- healthStatus: + status: Unknown + message: "Elastic Beat status is unknown. Ensure your ArgoCD is current and then check for/file a bug report: https://github.com/argoproj/argo-cd/issues" + inputPath: testdata/invalid.yaml diff --git a/resource_customizations/beat.k8s.elastic.co/Beat/testdata/invalid.yaml b/resource_customizations/beat.k8s.elastic.co/Beat/testdata/invalid.yaml new file mode 100644 index 0000000000000..3eca183165a5c --- /dev/null +++ b/resource_customizations/beat.k8s.elastic.co/Beat/testdata/invalid.yaml @@ -0,0 +1,12 @@ +apiVersion: beat.k8s.elastic.co/v1beta1 +kind: Beat +metadata: + name: quickstart +spec: + version: 8.8.8 + type: metricbeat +status: + expectedNodes: 1 + health: invalid + observedGeneration: 1 + version: 8.8.1 diff --git a/resource_customizations/beat.k8s.elastic.co/Beat/testdata/progressing.yaml b/resource_customizations/beat.k8s.elastic.co/Beat/testdata/progressing.yaml new file mode 100644 index 0000000000000..b007ad72ae3fe --- /dev/null +++ b/resource_customizations/beat.k8s.elastic.co/Beat/testdata/progressing.yaml @@ -0,0 +1,11 @@ +apiVersion: beat.k8s.elastic.co/v1beta1 +kind: Beat +metadata: + name: quickstart +spec: + version: 8.8.8 + type: metricbeat +status: + expectedNodes: 1 + observedGeneration: 1 + version: 8.8.1 diff --git a/resource_customizations/beat.k8s.elastic.co/Beat/testdata/ready_green.yaml b/resource_customizations/beat.k8s.elastic.co/Beat/testdata/ready_green.yaml new file mode 100644 index 0000000000000..3f3c1866793d8 --- /dev/null +++ b/resource_customizations/beat.k8s.elastic.co/Beat/testdata/ready_green.yaml @@ -0,0 +1,13 @@ +apiVersion: beat.k8s.elastic.co/v1beta1 +kind: Beat +metadata: + name: quickstart +spec: + version: 8.8.8 + type: metricbeat +status: + expectedNodes: 1 + availableNodes: 1 + health: green + observedGeneration: 1 + version: 8.8.1 diff --git a/resource_customizations/beat.k8s.elastic.co/Beat/testdata/ready_red.yaml b/resource_customizations/beat.k8s.elastic.co/Beat/testdata/ready_red.yaml new file mode 100644 index 0000000000000..fc2433c8076a8 --- /dev/null +++ b/resource_customizations/beat.k8s.elastic.co/Beat/testdata/ready_red.yaml @@ -0,0 +1,10 @@ +apiVersion: beat.k8s.elastic.co/v1beta1 +kind: Beat +metadata: + name: quickstart +spec: + version: 8.8.8 + type: metricbeat +status: + expectedNodes: 1 + health: red diff --git a/resource_customizations/beat.k8s.elastic.co/Beat/testdata/ready_yellow.yaml b/resource_customizations/beat.k8s.elastic.co/Beat/testdata/ready_yellow.yaml new file mode 100644 index 0000000000000..831ee281ef02d --- /dev/null +++ b/resource_customizations/beat.k8s.elastic.co/Beat/testdata/ready_yellow.yaml @@ -0,0 +1,11 @@ +apiVersion: beat.k8s.elastic.co/v1beta1 +kind: Beat +metadata: + name: quickstart +spec: + version: 8.8.8 + type: metricbeat +status: + availableNodes: 1 + expectedNodes: 2 + health: yellow diff --git a/resource_customizations/beat.k8s.elastic.co/Beat/testdata/ready_yellow_single_node.yaml b/resource_customizations/beat.k8s.elastic.co/Beat/testdata/ready_yellow_single_node.yaml new file mode 100644 index 0000000000000..d652b5a55d0ff --- /dev/null +++ b/resource_customizations/beat.k8s.elastic.co/Beat/testdata/ready_yellow_single_node.yaml @@ -0,0 +1,10 @@ +apiVersion: beat.k8s.elastic.co/v1beta1 +kind: Beat +metadata: + name: quickstart +spec: + version: 8.8.8 + type: metricbeat +status: + expectedNodes: 1 + health: yellow diff --git a/resource_customizations/beat.k8s.elastic.co/Beat/testdata/unknown.yaml b/resource_customizations/beat.k8s.elastic.co/Beat/testdata/unknown.yaml new file mode 100644 index 0000000000000..dbcca36c9e691 --- /dev/null +++ b/resource_customizations/beat.k8s.elastic.co/Beat/testdata/unknown.yaml @@ -0,0 +1,8 @@ +apiVersion: beat.k8s.elastic.co/v1beta1 +kind: Beat +metadata: + name: quickstart +spec: + version: 8.8.8 + type: metricbeat +status: {} diff --git a/resource_customizations/cloudfront.aws.crossplane.io/Distribution/health.lua b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/health.lua new file mode 100644 index 0000000000000..3e07226b3cf89 --- /dev/null +++ b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/health.lua @@ -0,0 +1,42 @@ +local hs = {} +if obj.status ~= nil then + if obj.status.conditions ~= nil then + local ready = false + local synced = false + local suspended = false + + for i, condition in ipairs(obj.status.conditions) do + + if condition.type == "Ready" then + ready = condition.status == "True" + ready_message = condition.reason + elseif condition.type == "Synced" then + synced = condition.status == "True" + if condition.reason == "ReconcileError" then + synced_message = condition.message + elseif condition.reason == "ReconcilePaused" then + suspended = true + suspended_message = condition.reason + end + end + end + if ready and synced then + hs.status = "Healthy" + hs.message = ready_message + elseif synced == false and suspended == true then + hs.status = "Suspended" + hs.message = suspended_message + elseif ready == false and synced == true and suspended == false then + hs.status = "Progressing" + hs.message = "Waiting for distribution to be available" + else + hs.status = "Degraded" + hs.message = synced_message + end + return hs + end +end + +hs.status = "Progressing" +hs.message = "Waiting for distribution to be created" +return hs \ No newline at end of file diff --git a/resource_customizations/cloudfront.aws.crossplane.io/Distribution/health_test.yaml b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/health_test.yaml new file mode 100644 index 0000000000000..981a6000ecb88 --- /dev/null +++ b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/health_test.yaml @@ -0,0 +1,37 @@ +tests: +- healthStatus: + status: Progressing + message: Waiting for distribution to be available + inputPath: testdata/progressing_creating.yaml +- healthStatus: + status: Progressing + message: Waiting for distribution to be available + inputPath: testdata/progressing_noavailable.yaml +- healthStatus: + status: Progressing + message: Waiting for distribution to be available + inputPath: testdata/progressing.yaml +- healthStatus: + status: Progressing + message: Waiting for distribution to be created + inputPath: testdata/progressing_noStatus.yaml +- healthStatus: + status: Degraded + message: > + update failed: cannot update Distribution in AWS: InvalidParameter: 2 + validation error(s) found. + + - missing required field, + UpdateDistributionInput.DistributionConfig.Origins.Items[0].DomainName. + + - missing required field, + UpdateDistributionInput.DistributionConfig.Origins.Items[0].Id. + inputPath: testdata/degraded_reconcileError.yaml +- healthStatus: + status: Suspended + message: ReconcilePaused + inputPath: testdata/suspended.yaml +- healthStatus: + status: Healthy + message: Available + inputPath: testdata/healthy.yaml diff --git a/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/degraded_reconcileError.yaml b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/degraded_reconcileError.yaml new file mode 100644 index 0000000000000..80ea7930574ac --- /dev/null +++ b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/degraded_reconcileError.yaml @@ -0,0 +1,96 @@ +apiVersion: cloudfront.aws.crossplane.io/v1alpha1 +kind: Distribution +metadata: + creationTimestamp: '2024-01-17T07:26:02Z' + generation: 2 + name: crossplane.io + resourceVersion: '261942288' + uid: 4b50c88b-165c-4176-be8e-aa28fdec0a94 +spec: + deletionPolicy: Orphan + forProvider: + distributionConfig: + comment: 'crossplane' + customErrorResponses: + items: [] + defaultCacheBehavior: + allowedMethods: + cachedMethods: + items: + - HEAD + - GET + items: + - HEAD + - GET + compress: false + defaultTTL: 600 + fieldLevelEncryptionID: '' + forwardedValues: + cookies: + forward: none + headers: + items: [] + queryString: false + queryStringCacheKeys: {} + functionAssociations: {} + lambdaFunctionAssociations: {} + maxTTL: 600 + minTTL: 0 + smoothStreaming: false + targetOriginID: crossplane.io + trustedKeyGroups: + enabled: false + trustedSigners: + enabled: false + viewerProtocolPolicy: allow-all + defaultRootObject: index.html + enabled: true + httpVersion: http2 + isIPV6Enabled: true + logging: + bucket: '' + enabled: false + includeCookies: false + prefix: '' + originGroups: {} + origins: + items: + - connectionAttempts: 3 + connectionTimeout: 10 + customOriginConfig: + httpPort: 8080 + httpSPort: 443 + originKeepaliveTimeout: 5 + originProtocolPolicy: http-only + originReadTimeout: 10 + originSSLProtocols: + items: + - TLSv1 + - TLSv1.1 + - TLSv1.2 + priceClass: PriceClass_200 + restrictions: + geoRestriction: + restrictionType: none + region: ap-northeast-2 + providerConfigRef: + name: crossplane +status: + conditions: + - lastTransitionTime: '2024-01-17T07:26:02Z' + message: > + update failed: cannot update Distribution in AWS: InvalidParameter: 2 + validation error(s) found. + + - missing required field, + UpdateDistributionInput.DistributionConfig.Origins.Items[0].DomainName. + + - missing required field, + UpdateDistributionInput.DistributionConfig.Origins.Items[0].Id. + reason: ReconcileError + status: 'False' + type: Synced + - lastTransitionTime: '2024-01-17T07:26:03Z' + reason: Available + status: 'True' + type: Ready diff --git a/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/healthy.yaml b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/healthy.yaml new file mode 100644 index 0000000000000..23d0287445e83 --- /dev/null +++ b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/healthy.yaml @@ -0,0 +1,92 @@ +apiVersion: cloudfront.aws.crossplane.io/v1alpha1 +kind: Distribution +metadata: + creationTimestamp: "2023-09-07T01:01:16Z" + generation: 121 + name: crossplane.io + resourceVersion: "254225966" + uid: 531d989c-a3d2-4ab4-841d-ab380cce0bdb +spec: + deletionPolicy: Orphan + forProvider: + distributionConfig: + comment: 'crossplane' + customErrorResponses: + items: [] + defaultCacheBehavior: + allowedMethods: + cachedMethods: + items: + - HEAD + - GET + items: + - HEAD + - GET + compress: false + defaultTTL: 600 + fieldLevelEncryptionID: '' + forwardedValues: + cookies: + forward: none + headers: + items: [] + queryString: false + queryStringCacheKeys: {} + functionAssociations: {} + lambdaFunctionAssociations: {} + maxTTL: 600 + minTTL: 0 + smoothStreaming: false + targetOriginID: crossplane.io + trustedKeyGroups: + enabled: false + trustedSigners: + enabled: false + viewerProtocolPolicy: allow-all + defaultRootObject: index.html + enabled: true + httpVersion: http2 + isIPV6Enabled: true + logging: + bucket: '' + enabled: false + includeCookies: false + prefix: '' + originGroups: {} + origins: + items: + - connectionAttempts: 3 + connectionTimeout: 10 + customHeaders: {} + customOriginConfig: + httpPort: 8080 + httpSPort: 443 + originKeepaliveTimeout: 5 + originProtocolPolicy: http-only + originReadTimeout: 10 + originSSLProtocols: + items: + - TLSv1 + - TLSv1.1 + - TLSv1.2 + domainName: crossplane.io + id: crossplane.io + originShield: + enabled: false + priceClass: PriceClass_200 + restrictions: + geoRestriction: + restrictionType: none + region: ap-northeast-2 + providerConfigRef: + name: crossplane +status: + conditions: + - lastTransitionTime: "2024-01-11T06:23:18Z" + reason: ReconcileSuccess + status: "True" + type: Synced + - lastTransitionTime: "2024-01-10T03:23:02Z" + reason: Available + status: "True" + type: Ready diff --git a/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/progressing.yaml b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/progressing.yaml new file mode 100644 index 0000000000000..3dbde7e040867 --- /dev/null +++ b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/progressing.yaml @@ -0,0 +1,92 @@ +apiVersion: cloudfront.aws.crossplane.io/v1alpha1 +kind: Distribution +metadata: + creationTimestamp: '2023-06-16T04:42:04Z' + generation: 37 + name: crossplane.io + resourceVersion: '254326453' + uid: fd357670-b762-4285-ae83-00859c40dd6b +spec: + deletionPolicy: Orphan + forProvider: + distributionConfig: + comment: 'crossplane' + customErrorResponses: + items: [] + defaultCacheBehavior: + allowedMethods: + cachedMethods: + items: + - HEAD + - GET + items: + - GET + - HEAD + compress: false + defaultTTL: 600 + fieldLevelEncryptionID: "" + forwardedValues: + cookies: + forward: none + headers: + items: [] + queryString: false + queryStringCacheKeys: {} + functionAssociations: {} + lambdaFunctionAssociations: {} + maxTTL: 600 + minTTL: 0 + smoothStreaming: false + targetOriginID: crossplane.io + trustedKeyGroups: + enabled: false + trustedSigners: + enabled: false + viewerProtocolPolicy: allow-all + defaultRootObject: index.html + enabled: true + httpVersion: http2 + isIPV6Enabled: true + logging: + bucket: "" + enabled: false + includeCookies: false + prefix: "" + originGroups: {} + origins: + items: + - connectionAttempts: 3 + connectionTimeout: 10 + customHeaders: {} + customOriginConfig: + httpPort: 8080 + httpSPort: 443 + originKeepaliveTimeout: 5 + originProtocolPolicy: http-only + originReadTimeout: 10 + originSSLProtocols: + items: + - TLSv1 + - TLSv1.1 + - TLSv1.2 + domainName: crossplane.io + id: crossplane.io + originShield: + enabled: false + priceClass: PriceClass_200 + restrictions: + geoRestriction: + restrictionType: none + region: ap-northeast-2 + providerConfigRef: + name: crossplane +status: + conditions: + - lastTransitionTime: '2024-01-11T08:11:27Z' + reason: Unavailable + status: 'False' + type: Ready + - lastTransitionTime: '2024-01-11T08:11:02Z' + reason: ReconcileSuccess + status: 'True' + type: Synced diff --git a/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/progressing_creating.yaml b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/progressing_creating.yaml new file mode 100644 index 0000000000000..122ab330d593b --- /dev/null +++ b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/progressing_creating.yaml @@ -0,0 +1,92 @@ +apiVersion: cloudfront.aws.crossplane.io/v1alpha1 +kind: Distribution +metadata: + creationTimestamp: "2023-09-07T01:01:16Z" + generation: 121 + name: crossplane.io + resourceVersion: "254225966" + uid: 531d989c-a3d2-4ab4-841d-ab380cce0bdb +spec: + deletionPolicy: Orphan + forProvider: + distributionConfig: + comment: 'crossplane' + customErrorResponses: + items: [] + defaultCacheBehavior: + allowedMethods: + cachedMethods: + items: + - HEAD + - GET + items: + - GET + - HEAD + compress: false + defaultTTL: 600 + fieldLevelEncryptionID: "" + forwardedValues: + cookies: + forward: none + headers: + items: [] + queryString: false + queryStringCacheKeys: {} + functionAssociations: {} + lambdaFunctionAssociations: {} + maxTTL: 600 + minTTL: 0 + smoothStreaming: false + targetOriginID: crossplane.io + trustedKeyGroups: + enabled: false + trustedSigners: + enabled: false + viewerProtocolPolicy: allow-all + defaultRootObject: index.html + enabled: true + httpVersion: http2 + isIPV6Enabled: true + logging: + bucket: "" + enabled: false + includeCookies: false + prefix: "" + originGroups: {} + origins: + items: + - connectionAttempts: 3 + connectionTimeout: 10 + customHeaders: {} + customOriginConfig: + httpPort: 8080 + httpSPort: 443 + originKeepaliveTimeout: 5 + originProtocolPolicy: http-only + originReadTimeout: 10 + originSSLProtocols: + items: + - TLSv1 + - TLSv1.1 + - TLSv1.2 + domainName: crossplane.io + id: crossplane.io + originShield: + enabled: false + priceClass: PriceClass_200 + restrictions: + geoRestriction: + restrictionType: none + region: ap-northeast-2 + providerConfigRef: + name: crossplane +status: + conditions: + - lastTransitionTime: "2023-11-16T04:44:27Z" + reason: Creating + status: "False" + type: Ready + - lastTransitionTime: "2023-11-16T04:44:25Z" + reason: ReconcileSuccess + status: "True" + type: Synced diff --git a/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/progressing_noStatus.yaml b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/progressing_noStatus.yaml new file mode 100644 index 0000000000000..2985ec2dea657 --- /dev/null +++ b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/progressing_noStatus.yaml @@ -0,0 +1,82 @@ +apiVersion: cloudfront.aws.crossplane.io/v1alpha1 +kind: Distribution +metadata: + creationTimestamp: "2023-09-07T01:01:16Z" + generation: 121 + name: crossplane.io + resourceVersion: "254225966" + uid: 531d989c-a3d2-4ab4-841d-ab380cce0bdb +spec: + deletionPolicy: Orphan + forProvider: + distributionConfig: + comment: 'crossplane' + customErrorResponses: + items: [] + defaultCacheBehavior: + allowedMethods: + cachedMethods: + items: + - HEAD + - GET + items: + - GET + - HEAD + compress: false + defaultTTL: 600 + fieldLevelEncryptionID: "" + forwardedValues: + cookies: + forward: none + headers: + items: [] + queryString: false + queryStringCacheKeys: {} + functionAssociations: {} + lambdaFunctionAssociations: {} + maxTTL: 600 + minTTL: 0 + smoothStreaming: false + targetOriginID: crossplane.io + trustedKeyGroups: + enabled: false + trustedSigners: + enabled: false + viewerProtocolPolicy: allow-all + defaultRootObject: index.html + enabled: true + httpVersion: http2 + isIPV6Enabled: true + logging: + bucket: "" + enabled: false + includeCookies: false + prefix: "" + originGroups: {} + origins: + items: + - connectionAttempts: 3 + connectionTimeout: 10 + customHeaders: {} + customOriginConfig: + httpPort: 8080 + httpSPort: 443 + originKeepaliveTimeout: 5 + originProtocolPolicy: http-only + originReadTimeout: 10 + originSSLProtocols: + items: + - TLSv1 + - TLSv1.1 + - TLSv1.2 + domainName: crossplane.io + id: crossplane.io + originShield: + enabled: false + priceClass: PriceClass_200 + restrictions: + geoRestriction: + restrictionType: none + region: ap-northeast-2 + providerConfigRef: + name: crossplane diff --git a/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/progressing_noavailable.yaml b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/progressing_noavailable.yaml new file mode 100644 index 0000000000000..7a47b0f48eea7 --- /dev/null +++ b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/progressing_noavailable.yaml @@ -0,0 +1,88 @@ +apiVersion: cloudfront.aws.crossplane.io/v1alpha1 +kind: Distribution +metadata: + generation: 1 + name: crossplane.io + resourceVersion: "261937039" + uid: a52c105f-b0e1-4027-aa19-7e93f269f2a6 +spec: + deletionPolicy: Orphan + forProvider: + distributionConfig: + comment: 'crossplane' + customErrorResponses: + items: [] + defaultCacheBehavior: + allowedMethods: + cachedMethods: + items: + - HEAD + - GET + items: + - GET + - HEAD + compress: false + defaultTTL: 600 + fieldLevelEncryptionID: "" + forwardedValues: + cookies: + forward: none + headers: + items: [] + queryString: false + queryStringCacheKeys: {} + functionAssociations: {} + lambdaFunctionAssociations: {} + maxTTL: 600 + minTTL: 0 + smoothStreaming: false + targetOriginID: crossplane.io + trustedKeyGroups: + enabled: false + trustedSigners: + enabled: false + viewerProtocolPolicy: allow-all + defaultRootObject: index.html + enabled: true + httpVersion: http2 + isIPV6Enabled: true + logging: + bucket: "" + enabled: false + includeCookies: false + prefix: "" + originGroups: {} + origins: + items: + - connectionAttempts: 3 + connectionTimeout: 10 + customHeaders: {} + customOriginConfig: + httpPort: 8080 + httpSPort: 443 + originKeepaliveTimeout: 5 + originProtocolPolicy: http-only + originReadTimeout: 10 + originSSLProtocols: + items: + - TLSv1 + - TLSv1.1 + - TLSv1.2 + domainName: crossplane.io + id: crossplane.io + originShield: + enabled: false + priceClass: PriceClass_200 + restrictions: + geoRestriction: + restrictionType: none + region: ap-northeast-2 + providerConfigRef: + name: crossplane +status: + atProvider: {} + conditions: + - lastTransitionTime: "2024-01-17T07:20:35Z" + reason: ReconcileSuccess + status: "True" + type: Synced diff --git a/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/suspended.yaml b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/suspended.yaml new file mode 100644 index 0000000000000..d15713737ff72 --- /dev/null +++ b/resource_customizations/cloudfront.aws.crossplane.io/Distribution/testdata/suspended.yaml @@ -0,0 +1,94 @@ +apiVersion: cloudfront.aws.crossplane.io/v1alpha1 +kind: Distribution +metadata: + annotations: + crossplane.io/paused: "true" + creationTimestamp: "2023-06-16T04:42:04Z" + generation: 34 + name: crossplane.io + resourceVersion: "254259056" + uid: fd357670-b762-4285-ae83-00859c40dd6b +spec: + deletionPolicy: Orphan + forProvider: + distributionConfig: + comment: 'crossplane' + customErrorResponses: + items: [] + defaultCacheBehavior: + allowedMethods: + cachedMethods: + items: + - HEAD + - GET + items: + - GET + - HEAD + compress: false + defaultTTL: 600 + fieldLevelEncryptionID: "" + forwardedValues: + cookies: + forward: none + headers: + items: [] + queryString: false + queryStringCacheKeys: {} + functionAssociations: {} + lambdaFunctionAssociations: {} + maxTTL: 600 + minTTL: 0 + smoothStreaming: false + targetOriginID: crossplane.io + trustedKeyGroups: + enabled: false + trustedSigners: + enabled: false + viewerProtocolPolicy: allow-all + defaultRootObject: index.html + enabled: true + httpVersion: http2 + isIPV6Enabled: true + logging: + bucket: "" + enabled: false + includeCookies: false + prefix: "" + originGroups: {} + origins: + items: + - connectionAttempts: 3 + connectionTimeout: 10 + customHeaders: {} + customOriginConfig: + httpPort: 8080 + httpSPort: 443 + originKeepaliveTimeout: 5 + originProtocolPolicy: http-only + originReadTimeout: 10 + originSSLProtocols: + items: + - TLSv1 + - TLSv1.1 + - TLSv1.2 + domainName: crossplane.io + id: crossplane.io + originShield: + enabled: false + priceClass: PriceClass_200 + restrictions: + geoRestriction: + restrictionType: none + region: ap-northeast-2 + providerConfigRef: + name: crossplane +status: + conditions: + - lastTransitionTime: "2023-10-16T07:40:47Z" + reason: Available + status: "True" + type: Ready + - lastTransitionTime: "2024-01-11T06:59:47Z" + reason: ReconcilePaused + status: "False" + type: Synced diff --git a/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/health_test.yaml b/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/health_test.yaml new file mode 100644 index 0000000000000..aa83951d5a2db --- /dev/null +++ b/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/health_test.yaml @@ -0,0 +1,25 @@ +tests: +- healthStatus: + status: Progressing + message: Waiting for resourcrecordset to be available + inputPath: testdata/progressing_creating.yaml +- healthStatus: + status: Progressing + message: Waiting for resourcrecordset to be created + inputPath: testdata/progressing_noStatus.yaml +- healthStatus: + status: Degraded + message: >- + create failed: failed to create the ResourceRecordSet resource: + InvalidChangeBatch: [RRSet of type CNAME with DNS name + www.crossplane.io. is not permitted as it conflicts with other + records with the same DNS name in zone crossplane.io.] + inputPath: testdata/degraded_reconcileError.yaml +- healthStatus: + status: Suspended + message: ReconcilePaused + inputPath: testdata/suspended_reconcilePaused.yaml +- healthStatus: + status: Healthy + message: Available + inputPath: testdata/healthy.yaml diff --git a/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/heatlh.lua b/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/heatlh.lua new file mode 100644 index 0000000000000..0cf5253e910ff --- /dev/null +++ b/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/heatlh.lua @@ -0,0 +1,41 @@ +local hs = {} +if obj.status ~= nil then + if obj.status.conditions ~= nil then + local ready = false + local synced = false + local suspended = false + for i, condition in ipairs(obj.status.conditions) do + + if condition.type == "Ready" then + ready = condition.status == "True" + ready_message = condition.reason + elseif condition.type == "Synced" then + synced = condition.status == "True" + if condition.reason == "ReconcileError" then + synced_message = condition.message + elseif condition.reason == "ReconcilePaused" then + suspended = true + suspended_message = condition.reason + end + end + end + if ready and synced then + hs.status = "Healthy" + hs.message = ready_message + elseif synced == false and suspended == true then + hs.status = "Suspended" + hs.message = suspended_message + elseif ready == false and synced == true and suspended == false then + hs.status = "Progressing" + hs.message = "Waiting for resourcrecordset to be available" + else + hs.status = "Degraded" + hs.message = synced_message + end + return hs + end +end + +hs.status = "Progressing" +hs.message = "Waiting for resourcrecordset to be created" +return hs diff --git a/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/testdata/degraded_reconcileError.yaml b/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/testdata/degraded_reconcileError.yaml new file mode 100644 index 0000000000000..31bc5123c7bfd --- /dev/null +++ b/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/testdata/degraded_reconcileError.yaml @@ -0,0 +1,35 @@ +apiVersion: route53.aws.crossplane.io/v1alpha1 +kind: ResourceRecordSet +metadata: + creationTimestamp: '2024-01-11T03:48:32Z' + generation: 1 + name: www-domain + resourceVersion: '187731157' + selfLink: /apis/route53.aws.crossplane.io/v1alpha1/resourcerecordsets/www-domain + uid: c9c85395-0830-4549-b255-e9e426663547 +spec: + providerConfigRef: + name: crossplane + forProvider: + resourceRecords: + - value: www.crossplane.io + setIdentifier: www + ttl: 60 + type: CNAME + weight: 0 + zoneId: ABCDEFGAB07CD +status: + conditions: + - lastTransitionTime: '2024-01-11T03:48:57Z' + message: >- + create failed: failed to create the ResourceRecordSet resource: + InvalidChangeBatch: [RRSet of type CNAME with DNS name + www.crossplane.io. is not permitted as it conflicts with other + records with the same DNS name in zone crossplane.io.] + reason: ReconcileError + status: 'False' + type: Synced + - lastTransitionTime: '2024-01-11T03:48:34Z' + reason: Creating + status: 'False' + type: Ready diff --git a/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/testdata/healthy.yaml b/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/testdata/healthy.yaml new file mode 100644 index 0000000000000..f808e46cc8c92 --- /dev/null +++ b/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/testdata/healthy.yaml @@ -0,0 +1,29 @@ +apiVersion: route53.aws.crossplane.io/v1alpha1 +kind: ResourceRecordSet +metadata: + creationTimestamp: "2023-11-16T04:44:19Z" + generation: 4 + name: www-domain + resourceVersion: "140397563" + selfLink: /apis/route53.aws.crossplane.io/v1alpha1/resourcerecordsets/www-domain + uid: 11f0d48d-134f-471b-9340-b6d45d953fcb +spec: + providerConfigRef: + name: crossplane + forProvider: + zoneId: A1B2C3D4 + type: A + aliasTarget: + dnsName: abcdefg.cloudfront.net. + evaluateTargetHealth: false + hostedZoneId: AZBZCZDEFG +status: + conditions: + - lastTransitionTime: "2023-11-16T04:44:27Z" + reason: Available + status: "True" + type: Ready + - lastTransitionTime: "2023-11-16T04:44:25Z" + reason: ReconcileSuccess + status: "True" + type: Synced diff --git a/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/testdata/progressing_creating.yaml b/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/testdata/progressing_creating.yaml new file mode 100644 index 0000000000000..abf59775fb8e0 --- /dev/null +++ b/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/testdata/progressing_creating.yaml @@ -0,0 +1,29 @@ +apiVersion: route53.aws.crossplane.io/v1alpha1 +kind: ResourceRecordSet +metadata: + creationTimestamp: "2023-11-16T04:44:19Z" + generation: 4 + name: www-domain + resourceVersion: "140397563" + selfLink: /apis/route53.aws.crossplane.io/v1alpha1/resourcerecordsets/www-domain + uid: 11f0d48d-134f-471b-9340-b6d45d953fcb +spec: + providerConfigRef: + name: crossplane + forProvider: + zoneId: A1B2C3D4 + type: A + aliasTarget: + dnsName: abcdefg.cloudfront.net. + evaluateTargetHealth: false + hostedZoneId: AZBZCZDEFG +status: + conditions: + - lastTransitionTime: "2023-11-16T04:44:27Z" + reason: Creating + status: "False" + type: Ready + - lastTransitionTime: "2023-11-16T04:44:25Z" + reason: ReconcileSuccess + status: "True" + type: Synced diff --git a/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/testdata/progressing_noStatus.yaml b/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/testdata/progressing_noStatus.yaml new file mode 100644 index 0000000000000..28d778d055050 --- /dev/null +++ b/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/testdata/progressing_noStatus.yaml @@ -0,0 +1,19 @@ +apiVersion: route53.aws.crossplane.io/v1alpha1 +kind: ResourceRecordSet +metadata: + creationTimestamp: "2023-11-16T04:44:19Z" + generation: 4 + name: www-domain + resourceVersion: "140397563" + selfLink: /apis/route53.aws.crossplane.io/v1alpha1/resourcerecordsets/www-domain + uid: 11f0d48d-134f-471b-9340-b6d45d953fcb +spec: + providerConfigRef: + name: crossplane + forProvider: + zoneId: A1B2C3D4 + type: A + aliasTarget: + dnsName: abcdefg.cloudfront.net. + evaluateTargetHealth: false + hostedZoneId: AZBZCZDEFG diff --git a/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/testdata/suspended_reconcilePaused.yaml b/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/testdata/suspended_reconcilePaused.yaml new file mode 100644 index 0000000000000..522c0e878dcf8 --- /dev/null +++ b/resource_customizations/route53.aws.crossplane.io/ResourceRecordSet/testdata/suspended_reconcilePaused.yaml @@ -0,0 +1,27 @@ +apiVersion: route53.aws.crossplane.io/v1alpha1 +kind: ResourceRecordSet +metadata: + annotations: + crossplane.io/paused: "true" + creationTimestamp: "2024-01-11T04:16:15Z" + generation: 1 + name: www-domain + resourceVersion: "187746011" + uid: 5517b419-5052-43d9-941e-c32f60d8c7e5 +spec: + providerConfigRef: + name: crossplane + forProvider: + resourceRecords: + - value: www.crossplane.io + setIdentifier: www + ttl: 60 + type: CNAME + weight: 0 + zoneId: ABCDEFGAB07CD +status: + conditions: + - lastTransitionTime: "2024-01-11T04:16:16Z" + reason: ReconcilePaused + status: "False" + type: Synced diff --git a/server/extension/extension.go b/server/extension/extension.go index aca924620756c..9f8edcd6184fc 100644 --- a/server/extension/extension.go +++ b/server/extension/extension.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/felixge/httpsnoop" log "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" @@ -300,6 +301,19 @@ type Manager struct { project ProjectGetter rbac RbacEnforcer registry ExtensionRegistry + metricsReg ExtensionMetricsRegistry +} + +// ExtensionMetricsRegistry exposes operations to update http metrics in the Argo CD +// API server. +type ExtensionMetricsRegistry interface { + // IncExtensionRequestCounter will increase the request counter for the given + // extension with the given status. + IncExtensionRequestCounter(extension string, status int) + // ObserveExtensionRequestDuration will register the request roundtrip duration + // between Argo CD API Server and the extension backend service for the given + // extension. + ObserveExtensionRequestDuration(extension string, duration time.Duration) } // NewManager will initialize a new manager. @@ -423,7 +437,8 @@ func validateConfigs(configs *ExtensionConfigs) error { } // NewProxy will instantiate a new reverse proxy based on the provided -// targetURL and config. +// targetURL and config. It will remove sensitive information from the +// incoming request such as the Authorization and Cookie headers. func NewProxy(targetURL string, headers []Header, config ProxyConfig) (*httputil.ReverseProxy, error) { url, err := url.Parse(targetURL) if err != nil { @@ -484,6 +499,10 @@ func (m *Manager) RegisterExtensions() error { if err != nil { return fmt.Errorf("error getting settings: %s", err) } + if settings.ExtensionConfig == "" { + m.log.Infof("No extensions configured.") + return nil + } err = m.UpdateExtensionRegistry(settings) if err != nil { return fmt.Errorf("error updating extension registry: %s", err) @@ -683,13 +702,26 @@ func (m *Manager) CallExtension() func(http.ResponseWriter, *http.Request) { prepareRequest(r, extName, app) m.log.Debugf("proxing request for extension %q", extName) - proxy.ServeHTTP(w, r) + // httpsnoop package is used to properly wrap the responseWriter + // and avoid optional intefaces issue: + // https://github.com/felixge/httpsnoop#why-this-package-exists + // CaptureMetrics will call the proxy and return the metrics from it. + metrics := httpsnoop.CaptureMetrics(proxy, w, r) + + go registerMetrics(extName, metrics, m.metricsReg) } } -// prepareRequest is reponsible for preparing and cleaning the given -// request, removing sensitive information before forwarding it to the -// proxy extension. +func registerMetrics(extName string, metrics httpsnoop.Metrics, extensionMetricsRegistry ExtensionMetricsRegistry) { + if extensionMetricsRegistry != nil { + extensionMetricsRegistry.IncExtensionRequestCounter(extName, metrics.Code) + extensionMetricsRegistry.ObserveExtensionRequestDuration(extName, metrics.Duration) + } +} + +// prepareRequest is reponsible for cleaning the incoming request URL removing +// the Argo CD extension API section from it. It will set the cluster destination name +// and cluster destination server in the headers as it is defined in the given app. func prepareRequest(r *http.Request, extName string, app *v1alpha1.Application) { r.URL.Path = strings.TrimPrefix(r.URL.Path, fmt.Sprintf("%s/%s", URLPrefix, extName)) if app.Spec.Destination.Name != "" { @@ -699,3 +731,8 @@ func prepareRequest(r *http.Request, extName string, app *v1alpha1.Application) r.Header.Set(HeaderArgoCDTargetClusterURL, app.Spec.Destination.Server) } } + +// AddMetricsRegistry will associate the given metricsReg in the Manager. +func (m *Manager) AddMetricsRegistry(metricsReg ExtensionMetricsRegistry) { + m.metricsReg = metricsReg +} diff --git a/server/extension/extension_test.go b/server/extension/extension_test.go index 273779d59ca29..ff287dde80424 100644 --- a/server/extension/extension_test.go +++ b/server/extension/extension_test.go @@ -8,6 +8,7 @@ import ( "net/http" "net/http/httptest" "strings" + "sync" "testing" "github.com/sirupsen/logrus/hooks/test" @@ -188,10 +189,6 @@ func TestRegisterExtensions(t *testing.T) { configYaml string } cases := []testCase{ - { - name: "no config", - configYaml: "", - }, { name: "no name", configYaml: getExtensionConfigNoName(), @@ -234,7 +231,7 @@ func TestRegisterExtensions(t *testing.T) { err := f.manager.RegisterExtensions() // then - assert.Error(t, err) + assert.Error(t, err, fmt.Sprintf("expected error in test %s but got nil", tc.name)) }) } }) @@ -247,6 +244,7 @@ func TestCallExtension(t *testing.T) { settingsGetterMock *mocks.SettingsGetter rbacMock *mocks.RbacEnforcer projMock *mocks.ProjectGetter + metricsMock *mocks.ExtensionMetricsRegistry manager *extension.Manager } defaultProjectName := "project-name" @@ -256,10 +254,12 @@ func TestCallExtension(t *testing.T) { settMock := &mocks.SettingsGetter{} rbacMock := &mocks.RbacEnforcer{} projMock := &mocks.ProjectGetter{} + metricsMock := &mocks.ExtensionMetricsRegistry{} logger, _ := test.NewNullLogger() logEntry := logger.WithContext(context.Background()) m := extension.NewManager(logEntry, settMock, appMock, projMock, rbacMock) + m.AddMetricsRegistry(metricsMock) mux := http.NewServeMux() extHandler := http.HandlerFunc(m.CallExtension()) @@ -271,6 +271,7 @@ func TestCallExtension(t *testing.T) { settingsGetterMock: settMock, rbacMock: rbacMock, projMock: projMock, + metricsMock: metricsMock, manager: m, } } @@ -328,6 +329,11 @@ func TestCallExtension(t *testing.T) { f.projMock.On("Get", prj.GetName()).Return(prj, nil) } + withMetrics := func(f *fixture) { + f.metricsMock.On("IncExtensionRequestCounter", mock.Anything, mock.Anything) + f.metricsMock.On("ObserveExtensionRequestDuration", mock.Anything, mock.Anything) + } + withRbac := func(f *fixture, allowApp, allowExt bool) { var appAccessError error var extAccessError error @@ -406,6 +412,18 @@ func TestCallExtension(t *testing.T) { proj := getProjectWithDestinations("project-name", nil, []string{clusterURL}) f.appGetterMock.On("Get", mock.Anything, mock.Anything).Return(app, nil) withProject(proj, f) + var wg sync.WaitGroup + wg.Add(2) + f.metricsMock. + On("IncExtensionRequestCounter", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + wg.Done() + }) + f.metricsMock. + On("ObserveExtensionRequestDuration", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + wg.Done() + }) // when resp, err := http.DefaultClient.Do(r) @@ -420,6 +438,13 @@ func TestCallExtension(t *testing.T) { assert.Equal(t, backendResponse, actual) assert.Equal(t, clusterURL, resp.Header.Get(extension.HeaderArgoCDTargetClusterURL)) assert.Equal(t, "Bearer some-bearer-token", resp.Header.Get("Authorization")) + + // waitgroup is necessary to make sure assertions aren't executed before + // the goroutine initiated by extension.CallExtension concludes which would + // lead to flaky test. + wg.Wait() + f.metricsMock.AssertCalled(t, "IncExtensionRequestCounter", backendEndpoint, http.StatusOK) + f.metricsMock.AssertCalled(t, "ObserveExtensionRequestDuration", backendEndpoint, mock.Anything) }) t.Run("proxy will return 404 if extension endpoint not registered", func(t *testing.T) { // given @@ -427,6 +452,7 @@ func TestCallExtension(t *testing.T) { f := setup() withExtensionConfig(getExtensionConfigString(), f) withRbac(f, true, true) + withMetrics(f) cluster1Name := "cluster1" f.appGetterMock.On("Get", "namespace", "app-name").Return(getApp(cluster1Name, "", defaultProjectName), nil) withProject(getProjectWithDestinations("project-name", []string{cluster1Name}, []string{"some-url"}), f) @@ -466,6 +492,7 @@ func TestCallExtension(t *testing.T) { withRbac(f, true, true) withExtensionConfig(getExtensionConfigWith2Backends(extName, beSrv1.URL, cluster1Name, beSrv2.URL, cluster2URL), f) withProject(getProjectWithDestinations("project-name", []string{cluster1Name}, []string{cluster2URL}), f) + withMetrics(f) ts := startTestServer(t, f) defer ts.Close() @@ -511,6 +538,7 @@ func TestCallExtension(t *testing.T) { extName := "some-extension" withRbac(f, allowApp, allowExtension) withExtensionConfig(getExtensionConfig(extName, "http://fake"), f) + withMetrics(f) ts := startTestServer(t, f) defer ts.Close() r := newExtensionRequest(t, "Get", fmt.Sprintf("%s/extensions/%s/", ts.URL, extName)) @@ -533,6 +561,7 @@ func TestCallExtension(t *testing.T) { extName := "some-extension" withRbac(f, allowApp, allowExtension) withExtensionConfig(getExtensionConfig(extName, "http://fake"), f) + withMetrics(f) ts := startTestServer(t, f) defer ts.Close() r := newExtensionRequest(t, "Get", fmt.Sprintf("%s/extensions/%s/", ts.URL, extName)) @@ -556,6 +585,7 @@ func TestCallExtension(t *testing.T) { noCluster := []string{} withRbac(f, allowApp, allowExtension) withExtensionConfig(getExtensionConfig(extName, "http://fake"), f) + withMetrics(f) ts := startTestServer(t, f) defer ts.Close() r := newExtensionRequest(t, "Get", fmt.Sprintf("%s/extensions/%s/", ts.URL, extName)) @@ -580,6 +610,7 @@ func TestCallExtension(t *testing.T) { extName := "some-extension" withRbac(f, allowApp, allowExtension) withExtensionConfig(getExtensionConfig(extName, "http://fake"), f) + withMetrics(f) ts := startTestServer(t, f) defer ts.Close() r := newExtensionRequest(t, "Get", fmt.Sprintf("%s/extensions/%s/", ts.URL, extName)) @@ -604,6 +635,7 @@ func TestCallExtension(t *testing.T) { differentProject := "differentProject" withRbac(f, allowApp, allowExtension) withExtensionConfig(getExtensionConfig(extName, "http://fake"), f) + withMetrics(f) ts := startTestServer(t, f) defer ts.Close() r := newExtensionRequest(t, "Get", fmt.Sprintf("%s/extensions/%s/", ts.URL, extName)) @@ -634,6 +666,7 @@ func TestCallExtension(t *testing.T) { withRbac(f, true, true) withExtensionConfig(getExtensionConfigWith2Backends(extName, "url1", "clusterName", "url2", "clusterURL"), f) withProject(getProjectWithDestinations("project-name", nil, []string{"srv1", destinationServer}), f) + withMetrics(f) ts := startTestServer(t, f) defer ts.Close() @@ -666,6 +699,7 @@ func TestCallExtension(t *testing.T) { differentProject := "differentProject" withRbac(f, allowApp, allowExtension) withExtensionConfig(getExtensionConfig(extName, "http://fake"), f) + withMetrics(f) ts := startTestServer(t, f) defer ts.Close() r := newExtensionRequest(t, "Get", fmt.Sprintf("%s/extensions/", ts.URL)) diff --git a/server/extension/mocks/ExtensionMetricsRegistry.go b/server/extension/mocks/ExtensionMetricsRegistry.go new file mode 100644 index 0000000000000..78e583929f74d --- /dev/null +++ b/server/extension/mocks/ExtensionMetricsRegistry.go @@ -0,0 +1,38 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + time "time" + + mock "github.com/stretchr/testify/mock" +) + +// ExtensionMetricsRegistry is an autogenerated mock type for the ExtensionMetricsRegistry type +type ExtensionMetricsRegistry struct { + mock.Mock +} + +// IncExtensionRequestCounter provides a mock function with given fields: _a0, status +func (_m *ExtensionMetricsRegistry) IncExtensionRequestCounter(_a0 string, status int) { + _m.Called(_a0, status) +} + +// ObserveExtensionRequestDuration provides a mock function with given fields: _a0, duration +func (_m *ExtensionMetricsRegistry) ObserveExtensionRequestDuration(_a0 string, duration time.Duration) { + _m.Called(_a0, duration) +} + +// NewExtensionMetricsRegistry creates a new instance of ExtensionMetricsRegistry. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewExtensionMetricsRegistry(t interface { + mock.TestingT + Cleanup(func()) +}) *ExtensionMetricsRegistry { + mock := &ExtensionMetricsRegistry{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/server/metrics/metrics.go b/server/metrics/metrics.go index 40698e742b093..4afac9da26c02 100644 --- a/server/metrics/metrics.go +++ b/server/metrics/metrics.go @@ -14,8 +14,10 @@ import ( type MetricsServer struct { *http.Server - redisRequestCounter *prometheus.CounterVec - redisRequestHistogram *prometheus.HistogramVec + redisRequestCounter *prometheus.CounterVec + redisRequestHistogram *prometheus.HistogramVec + extensionRequestCounter *prometheus.CounterVec + extensionRequestDuration *prometheus.HistogramVec } var ( @@ -34,6 +36,21 @@ var ( }, []string{"initiator"}, ) + extensionRequestCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "argocd_proxy_extension_request_total", + Help: "Number of requests sent to configured proxy extensions.", + }, + []string{"extension", "status"}, + ) + extensionRequestDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "argocd_proxy_extension_request_duration_seconds", + Help: "Request duration in seconds between the Argo CD API server and the extension backend.", + Buckets: []float64{0.1, 0.25, .5, 1, 2, 5, 10}, + }, + []string{"extension"}, + ) ) // NewMetricsServer returns a new prometheus server which collects api server metrics @@ -48,14 +65,18 @@ func NewMetricsServer(host string, port int) *MetricsServer { registry.MustRegister(redisRequestCounter) registry.MustRegister(redisRequestHistogram) + registry.MustRegister(extensionRequestCounter) + registry.MustRegister(extensionRequestDuration) return &MetricsServer{ Server: &http.Server{ Addr: fmt.Sprintf("%s:%d", host, port), Handler: mux, }, - redisRequestCounter: redisRequestCounter, - redisRequestHistogram: redisRequestHistogram, + redisRequestCounter: redisRequestCounter, + redisRequestHistogram: redisRequestHistogram, + extensionRequestCounter: extensionRequestCounter, + extensionRequestDuration: extensionRequestDuration, } } @@ -67,3 +88,11 @@ func (m *MetricsServer) IncRedisRequest(failed bool) { func (m *MetricsServer) ObserveRedisRequestDuration(duration time.Duration) { m.redisRequestHistogram.WithLabelValues("argocd-server").Observe(duration.Seconds()) } + +func (m *MetricsServer) IncExtensionRequestCounter(extension string, status int) { + m.extensionRequestCounter.WithLabelValues(extension, strconv.Itoa(status)).Inc() +} + +func (m *MetricsServer) ObserveExtensionRequestDuration(extension string, duration time.Duration) { + m.extensionRequestDuration.WithLabelValues(extension).Observe(duration.Seconds()) +} diff --git a/server/server.go b/server/server.go index 6ebbc9723167f..e42e6f59a49a3 100644 --- a/server/server.go +++ b/server/server.go @@ -197,6 +197,7 @@ type ArgoCDServer struct { type ArgoCDServerOpts struct { DisableAuth bool + ContentTypes []string EnableGZip bool Insecure bool StaticAssetsDir string @@ -222,6 +223,18 @@ type ArgoCDServerOpts struct { EnableProxyExtension bool } +// HTTPMetricsRegistry exposes operations to update http metrics in the Argo CD +// API server. +type HTTPMetricsRegistry interface { + // IncExtensionRequestCounter will increase the request counter for the given + // extension with the given status. + IncExtensionRequestCounter(extension string, status int) + // ObserveExtensionRequestDuration will register the request roundtrip duration + // between Argo CD API Server and the extension backend service for the given + // extension. + ObserveExtensionRequestDuration(extension string, duration time.Duration) +} + // initializeDefaultProject creates the default project if it does not already exist func initializeDefaultProject(opts ArgoCDServerOpts) error { defaultProj := &v1alpha1.AppProject{ @@ -483,6 +496,12 @@ func (a *ArgoCDServer) Init(ctx context.Context) { // golang/protobuf). func (a *ArgoCDServer) Run(ctx context.Context, listeners *Listeners) { a.userStateStorage.Init(ctx) + + metricsServ := metrics.NewMetricsServer(a.MetricsHost, a.MetricsPort) + if a.RedisClient != nil { + cacheutil.CollectMetrics(a.RedisClient, metricsServ) + } + svcSet := newArgoCDServiceSet(a) a.serviceSet = svcSet grpcS, appResourceTreeFn := a.newGRPCServer() @@ -491,9 +510,9 @@ func (a *ArgoCDServer) Run(ctx context.Context, listeners *Listeners) { var httpsS *http.Server if a.useTLS() { httpS = newRedirectServer(a.ListenPort, a.RootPath) - httpsS = a.newHTTPServer(ctx, a.ListenPort, grpcWebS, appResourceTreeFn, listeners.GatewayConn) + httpsS = a.newHTTPServer(ctx, a.ListenPort, grpcWebS, appResourceTreeFn, listeners.GatewayConn, metricsServ) } else { - httpS = a.newHTTPServer(ctx, a.ListenPort, grpcWebS, appResourceTreeFn, listeners.GatewayConn) + httpS = a.newHTTPServer(ctx, a.ListenPort, grpcWebS, appResourceTreeFn, listeners.GatewayConn, metricsServ) } if a.RootPath != "" { httpS.Handler = withRootPath(httpS.Handler, a) @@ -507,11 +526,6 @@ func (a *ArgoCDServer) Run(ctx context.Context, listeners *Listeners) { httpsS.Handler = &bug21955Workaround{handler: httpsS.Handler} } - metricsServ := metrics.NewMetricsServer(a.MetricsHost, a.MetricsPort) - if a.RedisClient != nil { - cacheutil.CollectMetrics(a.RedisClient, metricsServ) - } - // CMux is used to support servicing gRPC and HTTP1.1+JSON on the same port tcpm := cmux.New(listeners.Main) var tlsm cmux.CMux @@ -959,7 +973,7 @@ func compressHandler(handler http.Handler) http.Handler { // newHTTPServer returns the HTTP server to serve HTTP/HTTPS requests. This is implemented // using grpc-gateway as a proxy to the gRPC server. -func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandler http.Handler, appResourceTreeFn application.AppResourceTreeFn, conn *grpc.ClientConn) *http.Server { +func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandler http.Handler, appResourceTreeFn application.AppResourceTreeFn, conn *grpc.ClientConn, metricsReg HTTPMetricsRegistry) *http.Server { endpoint := fmt.Sprintf("localhost:%d", port) mux := http.NewServeMux() httpS := http.Server{ @@ -990,6 +1004,11 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl if a.EnableGZip { handler = compressHandler(handler) } + if len(a.ContentTypes) > 0 { + handler = enforceContentTypes(handler, a.ContentTypes) + } else { + log.WithField(common.SecurityField, common.SecurityHigh).Warnf("Content-Type enforcement is disabled, which may make your API vulnerable to CSRF attacks") + } mux.Handle("/api/", handler) terminal := application.NewHandler(a.appLister, a.Namespace, a.ApplicationNamespaces, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells, *a.sessionMgr). @@ -1003,7 +1022,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl // API server won't panic if extensions fail to register. In // this case an error log will be sent and no extension route // will be added in mux. - registerExtensions(mux, a) + registerExtensions(mux, a, metricsReg) } mustRegisterGWHandler(versionpkg.RegisterVersionServiceHandler, ctx, gwmux, conn) @@ -1056,16 +1075,32 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl return &httpS } +func enforceContentTypes(handler http.Handler, types []string) http.Handler { + allowedTypes := map[string]bool{} + for _, t := range types { + allowedTypes[strings.ToLower(t)] = true + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet || allowedTypes[strings.ToLower(r.Header.Get("Content-Type"))] { + handler.ServeHTTP(w, r) + } else { + http.Error(w, "Invalid content type", http.StatusUnsupportedMediaType) + } + }) +} + // registerExtensions will try to register all configured extensions // in the given mux. If any error is returned while registering // extensions handlers, no route will be added in the given mux. -func registerExtensions(mux *http.ServeMux, a *ArgoCDServer) { +func registerExtensions(mux *http.ServeMux, a *ArgoCDServer, metricsReg HTTPMetricsRegistry) { a.log.Info("Registering extensions...") extHandler := http.HandlerFunc(a.extensionManager.CallExtension()) authMiddleware := a.sessionMgr.AuthMiddlewareFunc(a.DisableAuth) // auth middleware ensures that requests to all extensions are authenticated first mux.Handle(fmt.Sprintf("%s/", extension.URLPrefix), authMiddleware(extHandler)) + a.extensionManager.AddMetricsRegistry(metricsReg) + err := a.extensionManager.RegisterExtensions() if err != nil { a.log.Errorf("Error registering extensions: %s", err) diff --git a/server/server_test.go b/server/server_test.go index acfb32e57e5d4..c4f4153f24d89 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -1526,3 +1526,46 @@ func TestReplaceBaseHRef(t *testing.T) { }) } } + +func Test_enforceContentTypes(t *testing.T) { + getBaseHandler := func(t *testing.T, allow bool) http.Handler { + return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + assert.True(t, allow, "http handler was hit when it should have been blocked by content type enforcement") + writer.WriteHeader(200) + }) + } + + t.Parallel() + + t.Run("GET - not providing a content type, should still succeed", func(t *testing.T) { + handler := enforceContentTypes(getBaseHandler(t, true), []string{"application/json"}).(http.HandlerFunc) + req := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + handler(w, req) + resp := w.Result() + assert.Equal(t, 200, resp.StatusCode) + }) + + t.Run("POST", func(t *testing.T) { + handler := enforceContentTypes(getBaseHandler(t, true), []string{"application/json"}).(http.HandlerFunc) + req := httptest.NewRequest("POST", "/", nil) + w := httptest.NewRecorder() + handler(w, req) + resp := w.Result() + assert.Equal(t, 415, resp.StatusCode, "didn't provide a content type, should have gotten an error") + + req = httptest.NewRequest("POST", "/", nil) + req.Header = map[string][]string{"Content-Type": {"application/json"}} + w = httptest.NewRecorder() + handler(w, req) + resp = w.Result() + assert.Equal(t, 200, resp.StatusCode, "should have passed, since an allowed content type was provided") + + req = httptest.NewRequest("POST", "/", nil) + req.Header = map[string][]string{"Content-Type": {"not-allowed"}} + w = httptest.NewRecorder() + handler(w, req) + resp = w.Result() + assert.Equal(t, 415, resp.StatusCode, "should not have passed, since a disallowed content type was provided") + }) +} diff --git a/test/e2e/app_sync_options_test.go b/test/e2e/app_sync_options_test.go new file mode 100644 index 0000000000000..7d0a0ffeabb99 --- /dev/null +++ b/test/e2e/app_sync_options_test.go @@ -0,0 +1,61 @@ +package e2e + +import ( + "testing" + + "github.com/argoproj/gitops-engine/pkg/health" + . "github.com/argoproj/gitops-engine/pkg/sync/common" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + + . "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" +) + +// Given application is set with --sync-option CreateNamespace=true and --sync-option ServerSideApply=true +// +// application --dest-namespace exists +// +// Then, --dest-namespace is created with server side apply +// application is synced and healthy with resource +// application resources created with server side apply in the newly created namespace. +func TestNamespaceCreationWithSSA(t *testing.T) { + SkipOnEnv(t, "OPENSHIFT") + namespace := "guestbook-ui-with-ssa" + defer func() { + if !t.Skipped() { + _, err := Run("", "kubectl", "delete", "namespace", namespace) + assert.NoError(t, err) + } + }() + + ctx := Given(t) + ctx. + SetAppNamespace(AppNamespace()). + Timeout(30). + Path("guestbook"). + When(). + CreateFromFile(func(app *Application) { + app.Spec.SyncPolicy = &SyncPolicy{ + SyncOptions: SyncOptions{"CreateNamespace=true", "ServerSideApply=true"}, + } + }). + Then(). + Expect(NoNamespace(namespace)). + When(). + AppSet("--dest-namespace", namespace). + Sync(). + Then(). + Expect(Success("")). + Expect(Namespace(namespace, func(app *Application, ns *v1.Namespace) { + assert.NotContains(t, ns.Annotations, "kubectl.kubernetes.io/last-applied-configuration") + })). + Expect(SyncStatusIs(SyncStatusCodeSynced)). + Expect(HealthIs(health.HealthStatusHealthy)). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(ResourceHealthWithNamespaceIs("Deployment", "guestbook-ui", namespace, health.HealthStatusHealthy)). + Expect(ResourceSyncStatusWithNamespaceIs("Deployment", "guestbook-ui", namespace, SyncStatusCodeSynced)). + Expect(ResourceHealthWithNamespaceIs("Service", "guestbook-ui", namespace, health.HealthStatusHealthy)). + Expect(ResourceSyncStatusWithNamespaceIs("Service", "guestbook-ui", namespace, SyncStatusCodeSynced)) +} diff --git a/test/e2e/cluster_test.go b/test/e2e/cluster_test.go index e57b2132b7472..2074a6aa1b7b1 100644 --- a/test/e2e/cluster_test.go +++ b/test/e2e/cluster_test.go @@ -38,7 +38,7 @@ https://kubernetes.default.svc in-cluster %v Successful `, GetVe When(). CreateApp() - tries := 2 + tries := 5 for i := 0; i <= tries; i += 1 { clusterFixture.GivenWithSameState(t). When(). diff --git a/test/e2e/fixture/applicationsets/context.go b/test/e2e/fixture/applicationsets/context.go index c10b2c99bfe5f..a7e91f4d0c8ff 100644 --- a/test/e2e/fixture/applicationsets/context.go +++ b/test/e2e/fixture/applicationsets/context.go @@ -5,7 +5,6 @@ import ( "time" "github.com/argoproj/argo-cd/v2/test/e2e/fixture/applicationsets/utils" - . "github.com/argoproj/argo-cd/v2/test/e2e/fixture/applicationsets/utils" ) // Context implements the "given" part of given/when/then @@ -19,7 +18,7 @@ type Context struct { } func Given(t *testing.T) *Context { - EnsureCleanState(t) + utils.EnsureCleanState(t) return &Context{t: t} } diff --git a/test/e2e/fixture/http.go b/test/e2e/fixture/http.go index 1e818f5262024..00c123ab5d893 100644 --- a/test/e2e/fixture/http.go +++ b/test/e2e/fixture/http.go @@ -28,6 +28,7 @@ func DoHttpRequest(method string, path string, data ...byte) (*http.Response, er return nil, err } req.AddCookie(&http.Cookie{Name: common.AuthCookieName, Value: token}) + req.Header.Set("Content-Type", "application/json") httpClient := &http.Client{ Transport: &http.Transport{ diff --git a/test/e2e/sync_waves_test.go b/test/e2e/sync_waves_test.go index ac5db15eee57d..8d0ee14e487d1 100644 --- a/test/e2e/sync_waves_test.go +++ b/test/e2e/sync_waves_test.go @@ -9,6 +9,8 @@ import ( "github.com/argoproj/gitops-engine/pkg/health" . "github.com/argoproj/gitops-engine/pkg/sync/common" + + v1 "k8s.io/api/core/v1" ) func TestFixingDegradedApp(t *testing.T) { @@ -100,3 +102,46 @@ func TestDegradedDeploymentIsSucceededAndSynced(t *testing.T) { Expect(SyncStatusIs(SyncStatusCodeSynced)). Expect(ResourceResultNumbering(1)) } + +// resources should be pruned in reverse of creation order(syncwaves order) +func TestSyncPruneOrderWithSyncWaves(t *testing.T) { + ctx := Given(t).Timeout(60) + + // remove finalizer to ensure proper cleanup if test fails at early stage + defer func() { + _, _ = RunCli("app", "patch-resource", ctx.AppQualifiedName(), + "--kind", "Pod", + "--resource-name", "pod-with-finalizers", + "--patch", `[{"op": "remove", "path": "/metadata/finalizers"}]`, + "--patch-type", "application/json-patch+json", "--all", + ) + }() + + ctx.Path("syncwaves-prune-order"). + When(). + CreateApp(). + // creation order: sa & role -> rolebinding -> pod + Sync(). + Wait(). + Then(). + Expect(SyncStatusIs(SyncStatusCodeSynced)). + Expect(HealthIs(health.HealthStatusHealthy)). + When(). + // delete files to remove resources + DeleteFile("pod.yaml"). + DeleteFile("rbac.yaml"). + Refresh(RefreshTypeHard). + IgnoreErrors(). + Then(). + Expect(SyncStatusIs(SyncStatusCodeOutOfSync)). + When(). + // prune order: pod -> rolebinding -> sa & role + Sync("--prune"). + Wait(). + Then(). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(SyncStatusIs(SyncStatusCodeSynced)). + Expect(HealthIs(health.HealthStatusHealthy)). + Expect(NotPod(func(p v1.Pod) bool { return p.Name == "pod-with-finalizers" })). + Expect(ResourceResultNumbering(4)) +} diff --git a/test/e2e/testdata/syncwaves-prune-order/README.md b/test/e2e/testdata/syncwaves-prune-order/README.md new file mode 100644 index 0000000000000..92a62fdfe109d --- /dev/null +++ b/test/e2e/testdata/syncwaves-prune-order/README.md @@ -0,0 +1,15 @@ +## Test Scenario + +This test example is for testing the reverse pruning of resources with syncwaves during sync operation. + +Resource creation happens in below order +- wave 0: sa & role +- wave 1: rolebinding +- wave 2: pod + +They are setup in such a way that the resources will be cleaned up properly only if they are deleted in the reverse order of creation i.e +- wave 0: pod +- wave 1: rolebinding +- wave 2: sa & role + +If above delete order is not followed the pod gets stuck in terminating state due to a finalizer which is supposed to be removed by k8s container lifecycle hook on delete if delete order is correct. \ No newline at end of file diff --git a/test/e2e/testdata/syncwaves-prune-order/pod.yaml b/test/e2e/testdata/syncwaves-prune-order/pod.yaml new file mode 100644 index 0000000000000..f801a3992aa37 --- /dev/null +++ b/test/e2e/testdata/syncwaves-prune-order/pod.yaml @@ -0,0 +1,41 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-with-finalizers + annotations: + argocd.argoproj.io/sync-wave: "2" + # remove this finalizers using container preStop lifecycle hook on delete + finalizers: + - example.com/block-delete +spec: + serviceAccountName: modify-pods-sa # sa with permissions to modify pods + terminationGracePeriodSeconds: 15 + containers: + - name: container + image: nginx:alpine + command: ["/bin/sh", "-c"] + args: ["sleep 10h"] + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + lifecycle: + # remove finalizers for successful delete of pod + preStop: + exec: + command: + - /bin/sh + - -c + - | + set -e + + SERVICE_ACCOUNT_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) + POD_URL="https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/pods/$POD_NAME" + PATCH_PAYLOAD='[{"op": "remove", "path": "/metadata/finalizers"}]' + + curl -k -v -H "Authorization: Bearer $SERVICE_ACCOUNT_TOKEN" -H "Content-Type: application/json-patch+json" -X PATCH --data "$PATCH_PAYLOAD" $POD_URL diff --git a/test/e2e/testdata/syncwaves-prune-order/rbac.yaml b/test/e2e/testdata/syncwaves-prune-order/rbac.yaml new file mode 100644 index 0000000000000..9512644b731db --- /dev/null +++ b/test/e2e/testdata/syncwaves-prune-order/rbac.yaml @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: modify-pods-sa + annotations: + argocd.argoproj.io/sync-wave: "0" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: modify-pods-role + annotations: + argocd.argoproj.io/sync-wave: "0" +rules: + - apiGroups: [""] + resources: + - pods + verbs: + - get + - list + - delete + - update + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: modify-pods-rolebinding + annotations: + argocd.argoproj.io/sync-wave: "1" +subjects: + - kind: ServiceAccount + name: modify-pods-sa +roleRef: + kind: Role + name: modify-pods-role + apiGroup: rbac.authorization.k8s.io \ No newline at end of file diff --git a/test/remote/Dockerfile b/test/remote/Dockerfile index 8d03d1321d25b..886a855f92597 100644 --- a/test/remote/Dockerfile +++ b/test/remote/Dockerfile @@ -1,6 +1,6 @@ ARG BASE_IMAGE=docker.io/library/ubuntu:22.04 -FROM docker.io/library/golang:1.21.3@sha256:02d7116222536a5cf0fcf631f90b507758b669648e0f20186d2dc94a9b419a9b AS go +FROM docker.io/library/golang:1.22.0@sha256:094e47ef90125eb49dfbc67d3480b56ee82ea9b05f50b750b5e85fab9606c2de AS go RUN go install github.com/mattn/goreman@latest && \ go install github.com/kisielk/godepgraph@latest diff --git a/ui-test/package.json b/ui-test/package.json index 1875e31b6fd62..fd34ca2edab4a 100644 --- a/ui-test/package.json +++ b/ui-test/package.json @@ -27,6 +27,6 @@ "tslint-config-prettier": "^1.18.0", "tslint-plugin-prettier": "^2.0.1", "typescript": "^4.0.3", - "yarn": "^1.22.10" + "yarn": "^1.22.13" } } diff --git a/ui-test/yarn.lock b/ui-test/yarn.lock index b80910028fb7f..6765cbf79d61b 100644 --- a/ui-test/yarn.lock +++ b/ui-test/yarn.lock @@ -540,9 +540,9 @@ flat@^5.0.2: integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== follow-redirects@^1.14.0: - version "1.14.9" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" - integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== + version "1.15.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" + integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== foreach@^2.0.5: version "2.0.5" @@ -1510,10 +1510,10 @@ yargs@13.3.2: y18n "^4.0.0" yargs-parser "^13.1.2" -yarn@^1.22.10: - version "1.22.10" - resolved "https://registry.npmjs.org/yarn/-/yarn-1.22.10.tgz" - integrity sha512-IanQGI9RRPAN87VGTF7zs2uxkSyQSrSPsju0COgbsKQOOXr5LtcVPeyXWgwVa0ywG3d8dg6kSYKGBuYK021qeA== +yarn@^1.22.13: + version "1.22.13" + resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.13.tgz#8789ef23b630fe99b819b044f4b7b93ab1bc1b8f" + integrity sha512-G8qG4t7Ef5cLVpzbM3HWWsow4hpfeSCfKtMnjfERmp9V5qSCOKz0uGAIQCM/x3gWfCzH8Bvb4hl3ZfhG/XD1Jg== yauzl@^2.10.0: version "2.10.0" diff --git a/ui/src/app/applications/components/application-details/application-resource-list.scss b/ui/src/app/applications/components/application-details/application-resource-list.scss new file mode 100644 index 0000000000000..9bc4b17bfe7ed --- /dev/null +++ b/ui/src/app/applications/components/application-details/application-resource-list.scss @@ -0,0 +1,13 @@ +.application-details__item { + display: flex; + + .application-details__item_text { + overflow: hidden; + text-overflow: ellipsis; + } + + .application-details__external_link { + flex: 0; + min-width: 13px; + } +} diff --git a/ui/src/app/applications/components/application-details/application-resource-list.tsx b/ui/src/app/applications/components/application-details/application-resource-list.tsx index c5519fc4b6ff9..d1e01adb52c04 100644 --- a/ui/src/app/applications/components/application-details/application-resource-list.tsx +++ b/ui/src/app/applications/components/application-details/application-resource-list.tsx @@ -10,6 +10,7 @@ import * as _ from 'lodash'; import Moment from 'react-moment'; import {format} from 'date-fns'; import {ResourceNode, ResourceRef} from '../../../shared/models'; +import './application-resource-list.scss'; export const ApplicationResourceList = ({ resources, @@ -89,8 +90,8 @@ export const ApplicationResourceList = ({
{ResourceLabel({kind: res.kind})}
-
- {res.name} +
+ {res.name} {res.kind === 'Application' && ( {ctx => ( diff --git a/ui/src/app/applications/components/application-summary/application-summary.tsx b/ui/src/app/applications/components/application-summary/application-summary.tsx index 5e8fa2db22ba1..4f372ef8f55c0 100644 --- a/ui/src/app/applications/components/application-summary/application-summary.tsx +++ b/ui/src/app/applications/components/application-summary/application-summary.tsx @@ -15,7 +15,7 @@ import { RevisionHelpIcon } from '../../../shared/components'; import {BadgePanel, Spinner} from '../../../shared/components'; -import {Consumer, ContextApis} from '../../../shared/context'; +import {AuthSettingsCtx, Consumer, ContextApis} from '../../../shared/context'; import * as models from '../../../shared/models'; import {services} from '../../../shared/services'; @@ -37,6 +37,16 @@ function swap(array: any[], a: number, b: number) { return array; } +function processPath(path: string) { + if (path !== null && path !== undefined) { + if (path === '.') { + return '(root)'; + } + return path; + } + return ''; +} + export interface ApplicationSummaryProps { app: models.Application; updateApp: (app: models.Application, query: {validate?: boolean}) => Promise; @@ -47,6 +57,7 @@ export const ApplicationSummary = (props: ApplicationSummaryProps) => { const source = getAppDefaultSource(app); const isHelm = source.hasOwnProperty('chart'); const initialState = app.spec.destination.server === undefined ? 'NAME' : 'URL'; + const useAuthSettingsCtx = React.useContext(AuthSettingsCtx); const [destFormat, setDestFormat] = React.useState(initialState); const [changeSync, setChangeSync] = React.useState(false); @@ -238,7 +249,7 @@ export const ApplicationSummary = (props: ApplicationSummaryProps) => { title: 'PATH', view: ( - {source.path ?? ''} + {processPath(source.path)} ), edit: (formApi: FormApi) => @@ -589,7 +600,7 @@ export const ApplicationSummary = (props: ApplicationSummaryProps) => {
)} - + { +export const BadgePanel = ({app, project, appNamespace, nsEnabled}: {app?: string; project?: string; appNamespace?: string; nsEnabled?: boolean}) => { const [badgeType, setBadgeType] = React.useState('URL'); const context = React.useContext(Context); if (!app && !project) { @@ -20,6 +20,9 @@ export const BadgePanel = ({app, project}: {app?: string; project?: string}) => let alt = ''; if (app) { badgeURL = `${root}api/badge?name=${app}&revision=true`; + if (nsEnabled) { + badgeURL += `&namespace=${appNamespace}`; + } entityURL = `${root}applications/${app}`; alt = 'App Status'; } else if (project) { diff --git a/ui/src/app/shared/services/repo-service.ts b/ui/src/app/shared/services/repo-service.ts index 09f8c169ac9ae..94378bee8352b 100644 --- a/ui/src/app/shared/services/repo-service.ts +++ b/ui/src/app/shared/services/repo-service.ts @@ -62,7 +62,9 @@ export class RepositoriesService { insecure, enableLfs, proxy, - project + project, + forceHttpBasicAuth, + enableOCI }: { type: string; name: string; @@ -75,10 +77,12 @@ export class RepositoriesService { enableLfs: boolean; proxy: string; project?: string; + forceHttpBasicAuth?: boolean; + enableOCI: boolean; }): Promise { return requests .put(`/repositories/${encodeURIComponent(url)}`) - .send({type, name, repo: url, username, password, tlsClientCertData, tlsClientCertKey, insecure, enableLfs, proxy, project}) + .send({type, name, repo: url, username, password, tlsClientCertData, tlsClientCertKey, insecure, enableLfs, proxy, project, forceHttpBasicAuth, enableOCI}) .then(res => res.body as models.Repository); } diff --git a/ui/src/app/shared/services/requests.ts b/ui/src/app/shared/services/requests.ts index 207917a318529..4df6d1e4ddf19 100644 --- a/ui/src/app/shared/services/requests.ts +++ b/ui/src/app/shared/services/requests.ts @@ -51,19 +51,19 @@ export default { }, post(url: string) { - return initHandlers(agent.post(`${apiRoot()}${url}`)); + return initHandlers(agent.post(`${apiRoot()}${url}`)).set('Content-Type', 'application/json'); }, put(url: string) { - return initHandlers(agent.put(`${apiRoot()}${url}`)); + return initHandlers(agent.put(`${apiRoot()}${url}`)).set('Content-Type', 'application/json'); }, patch(url: string) { - return initHandlers(agent.patch(`${apiRoot()}${url}`)); + return initHandlers(agent.patch(`${apiRoot()}${url}`)).set('Content-Type', 'application/json'); }, delete(url: string) { - return initHandlers(agent.del(`${apiRoot()}${url}`)); + return initHandlers(agent.del(`${apiRoot()}${url}`)).set('Content-Type', 'application/json'); }, loadEventSource(url: string): Observable { diff --git a/ui/src/app/ui-banner/ui-banner.tsx b/ui/src/app/ui-banner/ui-banner.tsx index 29dbc45eadfbc..ac097bca55163 100644 --- a/ui/src/app/ui-banner/ui-banner.tsx +++ b/ui/src/app/ui-banner/ui-banner.tsx @@ -116,7 +116,7 @@ export const Banner = (props: React.Props) => { chatUrl = 'invalid-url'; } } - const shouldRenderTop = position === 'top' || position === 'both'; + const shouldRenderTop = position === 'top' || position === 'both' || (!position && content); const shouldRenderBottom = position === 'bottom' || position === 'both'; return ( diff --git a/ui/yarn.lock b/ui/yarn.lock index 604bdfb107b04..346e47b078610 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -4520,9 +4520,9 @@ find-up@^4.0.0, find-up@^4.1.0: path-exists "^4.0.0" follow-redirects@^1.0.0: - version "1.14.9" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" - integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== + version "1.15.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" + integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== for-in@^1.0.2: version "1.0.2" diff --git a/util/cache/redis.go b/util/cache/redis.go index 7d5303bb3a9fa..4648a553f08cc 100644 --- a/util/cache/redis.go +++ b/util/cache/redis.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "io" + "net" "time" ioutil "github.com/argoproj/argo-cd/v2/util/io" @@ -159,41 +160,27 @@ type MetricsRegistry interface { ObserveRedisRequestDuration(duration time.Duration) } -var metricStartTimeKey = struct{}{} - type redisHook struct { registry MetricsRegistry } -func (rh *redisHook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) { - return context.WithValue(ctx, metricStartTimeKey, time.Now()), nil -} - -func (rh *redisHook) AfterProcess(ctx context.Context, cmd redis.Cmder) error { - cmdErr := cmd.Err() - rh.registry.IncRedisRequest(cmdErr != nil && cmdErr != redis.Nil) - - startTime := ctx.Value(metricStartTimeKey).(time.Time) - duration := time.Since(startTime) - rh.registry.ObserveRedisRequestDuration(duration) - - return nil -} - -func (redisHook) BeforeProcessPipeline(ctx context.Context, _ []redis.Cmder) (context.Context, error) { - return ctx, nil +func (rh *redisHook) DialHook(next redis.DialHook) redis.DialHook { + return func(ctx context.Context, network, addr string) (net.Conn, error) { + conn, err := next(ctx, network, addr) + return conn, err + } } -func (redisHook) AfterProcessPipeline(_ context.Context, _ []redis.Cmder) error { - return nil -} +func (rh *redisHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook { + return func(ctx context.Context, cmd redis.Cmder) error { + startTime := time.Now() -func (redisHook) DialHook(next redis.DialHook) redis.DialHook { - return nil -} + err := next(ctx, cmd) + rh.registry.IncRedisRequest(err != nil && err != redis.Nil) + rh.registry.ObserveRedisRequestDuration(time.Since(startTime)) -func (redisHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook { - return nil + return err + } } func (redisHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook { diff --git a/util/cache/redis_hook.go b/util/cache/redis_hook.go index 455ad03eb5bbf..e7cc3f4bcc68e 100644 --- a/util/cache/redis_hook.go +++ b/util/cache/redis_hook.go @@ -2,14 +2,13 @@ package cache import ( "context" - "strings" + "errors" + "net" "github.com/redis/go-redis/v9" log "github.com/sirupsen/logrus" ) -const NoSuchHostErr = "no such host" - type argoRedisHooks struct { reconnectCallback func() } @@ -18,32 +17,23 @@ func NewArgoRedisHook(reconnectCallback func()) *argoRedisHooks { return &argoRedisHooks{reconnectCallback: reconnectCallback} } -func (hook *argoRedisHooks) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) { - return ctx, nil -} - -func (hook *argoRedisHooks) AfterProcess(ctx context.Context, cmd redis.Cmder) error { - if cmd.Err() != nil && strings.Contains(cmd.Err().Error(), NoSuchHostErr) { - log.Warnf("Reconnect to redis because error: \"%v\"", cmd.Err()) - hook.reconnectCallback() - } - return nil -} - -func (hook *argoRedisHooks) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) { - return ctx, nil -} - -func (hook *argoRedisHooks) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) error { - return nil -} - func (hook *argoRedisHooks) DialHook(next redis.DialHook) redis.DialHook { - return nil + return func(ctx context.Context, network, addr string) (net.Conn, error) { + conn, err := next(ctx, network, addr) + return conn, err + } } func (hook *argoRedisHooks) ProcessHook(next redis.ProcessHook) redis.ProcessHook { - return nil + return func(ctx context.Context, cmd redis.Cmder) error { + var dnsError *net.DNSError + err := next(ctx, cmd) + if err != nil && errors.As(err, &dnsError) { + log.Warnf("Reconnect to redis because error: \"%v\"", err) + hook.reconnectCallback() + } + return err + } } func (hook *argoRedisHooks) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook { diff --git a/util/cache/redis_hook_test.go b/util/cache/redis_hook_test.go index ef9e6a1c85537..4d7d9b7aaf41d 100644 --- a/util/cache/redis_hook_test.go +++ b/util/cache/redis_hook_test.go @@ -1,38 +1,53 @@ package cache import ( - "context" - "errors" "testing" + "time" + "github.com/alicebob/miniredis/v2" "github.com/stretchr/testify/assert" "github.com/redis/go-redis/v9" ) func Test_ReconnectCallbackHookCalled(t *testing.T) { + mr, err := miniredis.Run() + if err != nil { + panic(err) + } + defer mr.Close() + called := false hook := NewArgoRedisHook(func() { called = true }) - cmd := &redis.StringCmd{} - cmd.SetErr(errors.New("Failed to resync revoked tokens. retrying again in 1 minute: dial tcp: lookup argocd-redis on 10.179.0.10:53: no such host")) - - _ = hook.AfterProcess(context.Background(), cmd) + faultyDNSRedisClient := redis.NewClient(&redis.Options{Addr: "invalidredishost.invalid:12345"}) + faultyDNSRedisClient.AddHook(hook) + faultyDNSClient := NewRedisCache(faultyDNSRedisClient, 60*time.Second, RedisCompressionNone) + err = faultyDNSClient.Set(&Item{Key: "baz", Object: "foo"}) assert.Equal(t, called, true) + assert.Error(t, err) } func Test_ReconnectCallbackHookNotCalled(t *testing.T) { + mr, err := miniredis.Run() + if err != nil { + panic(err) + } + defer mr.Close() + called := false hook := NewArgoRedisHook(func() { called = true }) - cmd := &redis.StringCmd{} - cmd.SetErr(errors.New("Something wrong")) - _ = hook.AfterProcess(context.Background(), cmd) + redisClient := redis.NewClient(&redis.Options{Addr: mr.Addr()}) + redisClient.AddHook(hook) + client := NewRedisCache(redisClient, 60*time.Second, RedisCompressionNone) + err = client.Set(&Item{Key: "foo", Object: "bar"}) assert.Equal(t, called, false) + assert.NoError(t, err) } diff --git a/util/cache/redis_test.go b/util/cache/redis_test.go index 3800753cee3ec..e05c7541f5ff1 100644 --- a/util/cache/redis_test.go +++ b/util/cache/redis_test.go @@ -2,14 +2,59 @@ package cache import ( "context" + "strconv" "testing" "time" + promcm "github.com/prometheus/client_model/go" + "github.com/alicebob/miniredis/v2" + "github.com/prometheus/client_golang/prometheus" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" ) +var ( + redisRequestCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "argocd_redis_request_total", + }, + []string{"initiator", "failed"}, + ) + redisRequestHistogram = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "argocd_redis_request_duration", + Buckets: []float64{0.1, 0.25, .5, 1, 2}, + }, + []string{"initiator"}, + ) +) + +type MockMetricsServer struct { + registry *prometheus.Registry + redisRequestCounter *prometheus.CounterVec + redisRequestHistogram *prometheus.HistogramVec +} + +func NewMockMetricsServer() *MockMetricsServer { + registry := prometheus.NewRegistry() + registry.MustRegister(redisRequestCounter) + registry.MustRegister(redisRequestHistogram) + return &MockMetricsServer{ + registry: registry, + redisRequestCounter: redisRequestCounter, + redisRequestHistogram: redisRequestHistogram, + } +} + +func (m *MockMetricsServer) IncRedisRequest(failed bool) { + m.redisRequestCounter.WithLabelValues("mock", strconv.FormatBool(failed)).Inc() +} + +func (m *MockMetricsServer) ObserveRedisRequestDuration(duration time.Duration) { + m.redisRequestHistogram.WithLabelValues("mock").Observe(duration.Seconds()) +} + func TestRedisSetCache(t *testing.T) { mr, err := miniredis.Run() if err != nil { @@ -70,3 +115,50 @@ func TestRedisSetCacheCompressed(t *testing.T) { assert.Equal(t, testValue, result) } + +func TestRedisMetrics(t *testing.T) { + mr, err := miniredis.Run() + if err != nil { + panic(err) + } + defer mr.Close() + + metric := &promcm.Metric{} + ms := NewMockMetricsServer() + redisClient := redis.NewClient(&redis.Options{Addr: mr.Addr()}) + faultyRedisClient := redis.NewClient(&redis.Options{Addr: "invalidredishost.invalid:12345"}) + CollectMetrics(redisClient, ms) + CollectMetrics(faultyRedisClient, ms) + + client := NewRedisCache(redisClient, 60*time.Second, RedisCompressionNone) + faultyClient := NewRedisCache(faultyRedisClient, 60*time.Second, RedisCompressionNone) + var res string + + //client successful request + err = client.Set(&Item{Key: "foo", Object: "bar"}) + assert.NoError(t, err) + err = client.Get("foo", &res) + assert.NoError(t, err) + + c, err := ms.redisRequestCounter.GetMetricWithLabelValues("mock", "false") + assert.NoError(t, err) + err = c.Write(metric) + assert.NoError(t, err) + assert.Equal(t, metric.Counter.GetValue(), float64(2)) + + //faulty client failed request + err = faultyClient.Get("foo", &res) + assert.Error(t, err) + c, err = ms.redisRequestCounter.GetMetricWithLabelValues("mock", "true") + assert.NoError(t, err) + err = c.Write(metric) + assert.NoError(t, err) + assert.Equal(t, metric.Counter.GetValue(), float64(1)) + + //both clients histogram count + o, err := ms.redisRequestHistogram.GetMetricWithLabelValues("mock") + assert.NoError(t, err) + err = o.(prometheus.Metric).Write(metric) + assert.NoError(t, err) + assert.Equal(t, int(metric.Histogram.GetSampleCount()), 3) +} diff --git a/util/db/repository_secrets.go b/util/db/repository_secrets.go index 31152300b0b8b..2d96c1c3a99eb 100644 --- a/util/db/repository_secrets.go +++ b/util/db/repository_secrets.go @@ -489,6 +489,9 @@ func (s *secretsRepositoryBackend) getRepositoryCredentialIndex(repoCredentials for i, cred := range repoCredentials { credUrl := git.NormalizeGitURL(string(cred.Data["url"])) if strings.HasPrefix(repoURL, credUrl) { + if len(credUrl) == max { + log.Warnf("Found multiple credentials for repoURL: %s", repoURL) + } if len(credUrl) > max { max = len(credUrl) idx = i diff --git a/util/helm/cmd.go b/util/helm/cmd.go index 419c7daff5d3c..cc2a1388d65a2 100644 --- a/util/helm/cmd.go +++ b/util/helm/cmd.go @@ -91,6 +91,28 @@ func (c *Cmd) RegistryLogin(repo string, creds Creds) (string, error) { args = append(args, "--password", creds.Password) } + if creds.CAPath != "" { + args = append(args, "--ca-file", creds.CAPath) + } + + if len(creds.CertData) > 0 { + filePath, closer, err := writeToTmp(creds.CertData) + if err != nil { + return "", err + } + defer argoio.Close(closer) + args = append(args, "--cert-file", filePath) + } + + if len(creds.KeyData) > 0 { + filePath, closer, err := writeToTmp(creds.KeyData) + if err != nil { + return "", err + } + defer argoio.Close(closer) + args = append(args, "--key-file", filePath) + } + if creds.InsecureSkipVerify { args = append(args, "--insecure") } @@ -238,6 +260,25 @@ func (c *Cmd) PullOCI(repo string, chart string, version string, destination str if creds.CAPath != "" { args = append(args, "--ca-file", creds.CAPath) } + + if len(creds.CertData) > 0 { + filePath, closer, err := writeToTmp(creds.CertData) + if err != nil { + return "", err + } + defer argoio.Close(closer) + args = append(args, "--cert-file", filePath) + } + + if len(creds.KeyData) > 0 { + filePath, closer, err := writeToTmp(creds.KeyData) + if err != nil { + return "", err + } + defer argoio.Close(closer) + args = append(args, "--key-file", filePath) + } + if creds.InsecureSkipVerify && c.insecureSkipVerifySupported { args = append(args, "--insecure-skip-tls-verify") } diff --git a/util/kustomize/kustomize.go b/util/kustomize/kustomize.go index 2487b14b4903b..f3d2246899d12 100644 --- a/util/kustomize/kustomize.go +++ b/util/kustomize/kustomize.go @@ -315,7 +315,7 @@ func (k *kustomize) Build(opts *v1alpha1.ApplicationSourceKustomize, kustomizeOp } func parseKustomizeBuildOptions(path, buildOptions string) []string { - return append([]string{"build", path}, strings.Split(buildOptions, " ")...) + return append([]string{"build", path}, strings.Fields(buildOptions)...) } var KustomizationNames = []string{"kustomization.yaml", "kustomization.yml", "Kustomization"} diff --git a/util/oidc/provider.go b/util/oidc/provider.go index fcb1a95b60f4f..d75bcf97efecd 100644 --- a/util/oidc/provider.go +++ b/util/oidc/provider.go @@ -73,6 +73,18 @@ func (p *providerImpl) newGoOIDCProvider() (*gooidc.Provider, error) { return prov, nil } +type tokenVerificationError struct { + errorsByAudience map[string]error +} + +func (t tokenVerificationError) Error() string { + var errorStrings []string + for aud, err := range t.errorsByAudience { + errorStrings = append(errorStrings, fmt.Sprintf("error for aud %q: %v", aud, err)) + } + return fmt.Sprintf("token verification failed for all audiences: %s", strings.Join(errorStrings, ", ")) +} + func (p *providerImpl) Verify(tokenString string, argoSettings *settings.ArgoCDSettings) (*gooidc.IDToken, error) { // According to the JWT spec, the aud claim is optional. The spec also says (emphasis mine): // @@ -104,6 +116,7 @@ func (p *providerImpl) Verify(tokenString string, argoSettings *settings.ArgoCDS if len(allowedAudiences) == 0 { return nil, errors.New("token has an audience claim, but no allowed audiences are configured") } + tokenVerificationErrors := make(map[string]error) // Token must be verified for at least one allowed audience for _, aud := range allowedAudiences { idToken, err = p.verify(aud, tokenString, false) @@ -117,6 +130,13 @@ func (p *providerImpl) Verify(tokenString string, argoSettings *settings.ArgoCDS if err == nil { break } + // We store the error for each audience so that we can return a more detailed error message to the user. + // If this gets merged, we'll be able to detect failures unrelated to audiences and short-circuit this loop + // to avoid logging irrelevant warnings: https://github.com/coreos/go-oidc/pull/406 + tokenVerificationErrors[aud] = err + } + if len(tokenVerificationErrors) > 0 { + err = tokenVerificationError{errorsByAudience: tokenVerificationErrors} } } diff --git a/util/test/testutil.go b/util/test/testutil.go index 1cb23bc08bb3e..bb6c43313358c 100644 --- a/util/test/testutil.go +++ b/util/test/testutil.go @@ -8,9 +8,9 @@ import ( "net/http/httptest" "testing" + "github.com/go-jose/go-jose/v3" "github.com/golang-jwt/jwt/v4" "github.com/stretchr/testify/require" - "gopkg.in/square/go-jose.v2" ) // Cert is a certificate for tests. It was generated like this: