diff --git a/.changelog/4255.txt b/.changelog/4255.txt deleted file mode 100644 index 1960697afa..0000000000 --- a/.changelog/4255.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -sync-catalog: Enable the user to purge the registered services by passing parent node and necessary filters. -``` \ No newline at end of file diff --git a/.changelog/4315.txt b/.changelog/4315.txt new file mode 100644 index 0000000000..05b7fb01da --- /dev/null +++ b/.changelog/4315.txt @@ -0,0 +1,3 @@ +```release-note:bug +helm: fix issue where the API Gateway GatewayClassConfig tolerations can not be parsed by the Helm chart. +``` diff --git a/.changelog/4316.txt b/.changelog/4316.txt new file mode 100644 index 0000000000..5397ebd093 --- /dev/null +++ b/.changelog/4316.txt @@ -0,0 +1,5 @@ +```release-note:bug +api-gateway: `global.imagePullSecrets` are now configured on the `ServiceAccount` for `Gateways`. + +Note: the referenced image pull Secret(s) must be present in the same namespace the `Gateway` is deployed to. +``` diff --git a/.changelog/4333.txt b/.changelog/4333.txt new file mode 100644 index 0000000000..bf9ff0167a --- /dev/null +++ b/.changelog/4333.txt @@ -0,0 +1,3 @@ +```release-note:improvement +helm: Exclude gke namespaces from being connect-injected when the connect-inject: default: true value is set. +``` diff --git a/.changelog/4378.txt b/.changelog/4378.txt new file mode 100644 index 0000000000..30a4287816 --- /dev/null +++ b/.changelog/4378.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +catalog-sync: Added field to helm chart to purge all services registered with catalog-sync from consul on disabling of catalog-sync. +``` diff --git a/.changelog/4385.txt b/.changelog/4385.txt new file mode 100644 index 0000000000..2d45c62919 --- /dev/null +++ b/.changelog/4385.txt @@ -0,0 +1,6 @@ +```release-note:security +crd: Add `http.incoming.requestNormalization` to the Mesh CRD to support configuring service traffic request normalization. +``` +```release-note:security +crd: Add `contains` and `ignoreCase` to the Intentions CRD to support configuring L7 Header intentions resilient to variable casing and multiple header values. +``` diff --git a/.changelog/4426.txt b/.changelog/4426.txt new file mode 100644 index 0000000000..8bad4b8e9d --- /dev/null +++ b/.changelog/4426.txt @@ -0,0 +1,3 @@ +```release-note:bug +cli: fix issue where the `consul-k8s proxy list` command does not include API gateways. +``` diff --git a/.changelog/4432.txt b/.changelog/4432.txt new file mode 100644 index 0000000000..6532d089ac --- /dev/null +++ b/.changelog/4432.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +cli: Introduce `gateway read` for collecting multiple components of a gateway's configuration by running a single command. +``` diff --git a/.changelog/4433.txt b/.changelog/4433.txt new file mode 100644 index 0000000000..d9932af4a2 --- /dev/null +++ b/.changelog/4433.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +cli: Introduce `gateway list` for collecting multiple components of all gateways' configuration by running a single command. +``` diff --git a/.changelog/4434.txt b/.changelog/4434.txt new file mode 100644 index 0000000000..e87e04cc21 --- /dev/null +++ b/.changelog/4434.txt @@ -0,0 +1,3 @@ +```release-note:bug +connect-inject: fix issue where the ACL policy for the connect-injector included the `acl = "write"` rule twice when namespaces were not enabled. +``` diff --git a/.github/scripts/check_skip_ci.sh b/.github/scripts/check_skip_ci.sh deleted file mode 100755 index da05d7fefe..0000000000 --- a/.github/scripts/check_skip_ci.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -set -euo pipefail - -# first argument is the list, second is the item to check -function contains() { - list=($1) - for item in "${list[@]}"; do - if [ "$item" == "$2" ]; then - return 0 - fi - done - return 1 -} - -# Get the list of changed files -# Using `git merge-base` ensures that we're always comparing against the correct branch point. -#For example, given the commits: -# -# A---B---C---D---W---X---Y---Z # origin/main -# \---E---F # feature/branch -# -# ... `git merge-base origin/$SKIP_CHECK_BRANCH HEAD` would return commit `D` -# `...HEAD` specifies from the common ancestor to the latest commit on the current branch (HEAD).. -skip_check_branch=${SKIP_CHECK_BRANCH:?SKIP_CHECK_BRANCH is required} -files_to_check=$(git diff --name-only "$(git merge-base origin/$skip_check_branch HEAD~)"...HEAD) - -# Define the directories to check -skipped_directories=("assets" ".changelog" "version") - -files_to_skip=("LICENSE" ".copywrite.hcl" ".gitignore") - -# Loop through the changed files and find directories/files outside the skipped ones -files_to_check_array=($files_to_check) -for file_to_check in "${files_to_check_array[@]}"; do - file_is_skipped=false - echo "checking file: $file_to_check" - - # Allow changes to: - # - This script - # - Files in the skipped directories - # - Markdown files - for dir in "${skipped_directories[@]}"; do - if [[ "$file_to_check" == */check_skip_ci.sh ]] || - [[ "$file_to_check" == "$dir/"* ]] || - [[ "$file_to_check" == *.md ]] || - contains "${files_to_skip[*]}" "$file_to_check"; then - file_is_skipped=true - break - fi - done - - if [ "$file_is_skipped" != "true" ]; then - echo -e "non-skippable file changed: $file_to_check" - SKIP_CI=false - echo "Changes detected in non-documentation files - will not skip tests and build" - echo "skip-ci=false" >>"$GITHUB_OUTPUT" - exit 0 ## if file is outside of the skipped_directory exit script - fi -done - -echo "Changes detected in only documentation files - skipping tests and build" -echo "skip-ci=true" >>"$GITHUB_OUTPUT" diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index d0d72cb6b3..3719afe1f8 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -40,6 +40,7 @@ jobs: - check-name: acceptance-cni - check-name: acceptance-tproxy - check-name: Unit test helm templates + - check-name: Unit test helm gen - check-name: Unit test enterprise control plane - check-name: Unit test control plane - check-name: Unit test cli diff --git a/.github/workflows/reusable-conditional-skip.yml b/.github/workflows/reusable-conditional-skip.yml index ef469ee9aa..113649fd6f 100644 --- a/.github/workflows/reusable-conditional-skip.yml +++ b/.github/workflows/reusable-conditional-skip.yml @@ -12,13 +12,58 @@ jobs: runs-on: ubuntu-latest name: Check whether to skip build and tests outputs: - skip-ci: ${{ steps.check-changed-files.outputs.skip-ci }} - env: - SKIP_CHECK_BRANCH: ${{ github.head_ref || github.ref_name }} + skip-ci: ${{ steps.maybe-skip-ci.outputs.skip-ci }} steps: + # We only allow use of conditional skip in two scenarios: + # 1. PRs + # 2. Pushes (merges) to protected branches (`main`, `release/**`) + # + # The second scenario is the only place we can be sure that checking just the + # latest change on the branch is sufficient. In PRs, we need to check _all_ commits. + # The ability to do this is ultimately determined by the triggers of the calling + # workflow, since `base_ref` (the target branch of a PR) is only available in + # `pull_request` events, not `push`. + - name: Error if conditional check is not allowed + if: ${{ !github.base_ref && !github.ref_protected }} + run: | + echo "Conditional skip requires a PR event with 'base_ref' or 'push' to a protected branch." + echo "github.base_ref: ${{ github.base_ref }}" + echo "github.ref_protected: ${{ github.ref_protected }}" + echo "github.ref_name: ${{ github.ref_name }}" + echo "Check the triggers of the calling workflow to ensure that these requirements are met." + exit 1 - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: fetch-depth: 0 - - name: Check changed files - id: check-changed-files - run: ./.github/scripts/check_skip_ci.sh \ No newline at end of file + - name: Check for skippable file changes + id: changed-files + uses: tj-actions/changed-files@e9772d140489982e0e3704fea5ee93d536f1e275 # v45.0.1 + with: + # This is a multi-line YAML string with one match pattern per line. + # Do not use quotes around values, as it's not supported. + # See https://github.com/tj-actions/changed-files/blob/main/README.md#inputs-%EF%B8%8F + # for usage, options, and more details on match syntax. + files: | + .github/workflows/reusable-conditional-skip.yml + LICENSE + .copywrite.hcl + .gitignore + **.md + assets/** + .changelog/** + - name: Print changed files + env: + SKIPPABLE_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} + NON_SKIPPABLE_FILES: ${{ steps.changed-files.outputs.other_changed_files }} + run: | + echo "Skippable changed files:" + for file in ${SKIPPABLE_CHANGED_FILES}; do echo " $file"; done + echo + echo "Non-skippable files:" + for file in ${NON_SKIPPABLE_FILES}; do echo " $file"; done + - name: Skip tests and build if only skippable files changed + id: maybe-skip-ci + if: ${{ steps.changed-files.outputs.only_changed == 'true' }} + run: | + echo "Skipping tests and build because only skippable files changed" + echo "skip-ci=true" >> $GITHUB_OUTPUT \ No newline at end of file diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 2b73015e42..e75ffec40d 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -1,3 +1,5 @@ +# This job runs a non-blocking informational security scan on the repository. +# For release-blocking security scans, see .release/security-scan.hcl. name: Security Scan on: @@ -9,6 +11,8 @@ on: branches: - main - release/** + # paths-ignore only works for non-required checks. + # Jobs that are required for merge must use reusable-conditional-skip.yml. paths-ignore: - 'assets/**' - '.changelog/**' diff --git a/.github/workflows/weekly-acceptance-1-6-x.yml b/.github/workflows/weekly-acceptance-1-6-x.yml new file mode 100644 index 0000000000..cb75d883f4 --- /dev/null +++ b/.github/workflows/weekly-acceptance-1-6-x.yml @@ -0,0 +1,28 @@ +# Dispatch to the consul-k8s-workflows with a weekly cron +# +# A separate file is needed for each release because the cron schedules are different for each release. +name: weekly-acceptance-1-6-x +on: + schedule: + # * is a special character in YAML so you have to quote this string + # Run weekly on Friday at 3AM UTC/11PM EST/8PM PST + - cron: '0 3 * * 6' + +# these should be the only settings that you will ever need to change +env: + BRANCH: "release/1.6.x" + CONTEXT: "weekly" + +jobs: + cloud: + name: cloud + runs-on: ubuntu-latest + steps: + - uses: benc-uk/workflow-dispatch@25b02cc069be46d637e8fe2f1e8484008e9e9609 # v1.2.3 + name: cloud + with: + workflow: cloud.yml + repo: hashicorp/consul-k8s-workflows + ref: main + token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ github.sha }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' diff --git a/.release/security-scan.hcl b/.release/security-scan.hcl index 5ae5e513d2..aa1994ca7c 100644 --- a/.release/security-scan.hcl +++ b/.release/security-scan.hcl @@ -40,7 +40,8 @@ binary { "GHSA-r53h-jv2g-vpx6", "CVE-2024-26147", # alias "GHSA-jw44-4f3j-q396", # Tracked in NET-8174 - "CVE-2019-25210" # alias + "CVE-2019-25210", # alias + "GO-2022-0635" ] } } diff --git a/CHANGELOG.md b/CHANGELOG.md index dce1f25d0a..ef6a780098 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,81 @@ +## 1.6.2 (January 7, 2025) + +IMPROVEMENTS: + +* cli: Introduce `gateway list` for collecting multiple components of all gateways' configuration by running a single command. [[GH-4433](https://github.com/hashicorp/consul-k8s/issues/4433)] +* cli: Introduce `gateway read` for collecting multiple components of a gateway's configuration by running a single command. [[GH-4432](https://github.com/hashicorp/consul-k8s/issues/4432)] +* Updated consul/api, envoyextensions & troubleshoot submodules [[PR-4451](https://github.com/hashicorp/consul-k8s/pull/4451)] + +BUG FIXES: + +* cli: fix issue where the `consul-k8s proxy list` command does not include API gateways. [[GH-4426](https://github.com/hashicorp/consul-k8s/issues/4426)] +* connect-inject: fix issue where the ACL policy for the connect-injector included the `acl = "write"` rule twice when namespaces were not enabled. [[GH-4434](https://github.com/hashicorp/consul-k8s/issues/4434)] + +SECURITY: + +* updated golang.org/x/net dependency to 0.34.0 to fix vulnerability [[GO-2024-3333](https://pkg.go.dev/vuln/GO-2024-3333)] in CLI, CNI, acceptance and control-plane submodule.[[PR-4452](https://github.com/hashicorp/consul-k8s/pull/4452)] + + +## 1.6.1 (November 4, 2023) + +SECURITY: + +* crd: Add `contains` and `ignoreCase` to the Intentions CRD to support configuring L7 Header intentions resilient to variable casing and multiple header values. [[GH-4385](https://github.com/hashicorp/consul-k8s/issues/4385)] +* crd: Add `http.incoming.requestNormalization` to the Mesh CRD to support configuring service traffic request normalization. [[GH-4385](https://github.com/hashicorp/consul-k8s/issues/4385)] + +IMPROVEMENTS: + +* catalog-sync: Added field to helm chart to purge all services registered with catalog-sync from consul on disabling of catalog-sync. [[GH-4378](https://github.com/hashicorp/consul-k8s/issues/4378)] + +BUG FIXES: + +* api-gateway: `global.imagePullSecrets` are now configured on the `ServiceAccount` for `Gateways`. + +Note: the referenced image pull Secret(s) must be present in the same namespace the `Gateway` is deployed to. [[GH-4316](https://github.com/hashicorp/consul-k8s/issues/4316)] +* helm: fix issue where the API Gateway GatewayClassConfig tolerations can not be parsed by the Helm chart. [[GH-4315](https://github.com/hashicorp/consul-k8s/issues/4315)] + +## 1.6.0 (October 16, 2024) + +> NOTE: Consul K8s 1.6.x is compatible with Consul 1.20.x and Consul Dataplane 1.6.x. Refer to our [compatibility matrix](https://developer.hashicorp.com/consul/docs/k8s/compatibility) for more info. + +SECURITY: + +* Upgrade Go to use 1.22.7. This addresses CVE + [CVE-2024-34155](https://nvd.nist.gov/vuln/detail/CVE-2024-34155) [[GH-4313](https://github.com/hashicorp/consul-k8s/issues/4313)] + +IMPROVEMENTS: + +* dns-proxy: add the ability to deploy a DNS proxy within the kubernetes cluster that forwards DNS requests to the consul server and can be configured with an ACL token and make partition aware DNS requests. [[GH-4300](https://github.com/hashicorp/consul-k8s/issues/4300)] +* sync-catalog: expose prometheus scrape metrics on sync-catalog pods [[GH-4212](https://github.com/hashicorp/consul-k8s/issues/4212)] +* connect-inject: remove unnecessary resource permissions from connect-inject ClusterRole [[GH-4307](https://github.com/hashicorp/consul-k8s/issues/4307)] +* helm: Exclude gke namespaces from being connect-injected when the connect-inject: default: true value is set. [[GH-4333](https://github.com/hashicorp/consul-k8s/issues/4333)] + +BUG FIXES: + +* control-plane: add missing `$HOST_IP` environment variable to consul-dataplane sidecar containers [[GH-4277](https://github.com/hashicorp/consul-k8s/issues/4277)] +* helm: Fix ArgoCD hooks related annotations on server-acl-init Job, they must be added at Job definition and not template level. [[GH-3989](https://github.com/hashicorp/consul-k8s/issues/3989)] +* sync-catalog: Enable the user to purge the registered services by passing parent node and necessary filters. [[GH-4255](https://github.com/hashicorp/consul-k8s/issues/4255)] + +## 1.6.0-rc1 (September 20, 2024) + +SECURITY: + +* Upgrade Go to use 1.22.7. This addresses CVE +[CVE-2024-34155](https://nvd.nist.gov/vuln/detail/CVE-2024-34155) [[GH-4313](https://github.com/hashicorp/consul-k8s/issues/4313)] + +IMPROVEMENTS: + +* dns-proxy: add the ability to deploy a DNS proxy within the kubernetes cluster that forwards DNS requests to the consul server and can be configured with an ACL token and make partition aware DNS requests. [[GH-4300](https://github.com/hashicorp/consul-k8s/issues/4300)] +* sync-catalog: expose prometheus scrape metrics on sync-catalog pods [[GH-4212](https://github.com/hashicorp/consul-k8s/issues/4212)] +* connect-inject: remove unnecessary resource permissions from connect-inject ClusterRole [[GH-4307](https://github.com/hashicorp/consul-k8s/issues/4307)] +* helm: Exclude gke namespaces from being connect-injected when the connect-inject: default: true value is set. [[GH-4333](https://github.com/hashicorp/consul-k8s/issues/4333)] + +BUG FIXES: + +* control-plane: add missing `$HOST_IP` environment variable to consul-dataplane sidecar containers [[GH-4277](https://github.com/hashicorp/consul-k8s/issues/4277)] +* helm: Fix ArgoCD hooks related annotations on server-acl-init Job, they must be added at Job definition and not template level. [[GH-3989](https://github.com/hashicorp/consul-k8s/issues/3989)] +* sync-catalog: Enable the user to purge the registered services by passing parent node and necessary filters. [[GH-4255](https://github.com/hashicorp/consul-k8s/issues/4255)] + ## 1.5.3 (August 30, 2024) SECURITY: diff --git a/Makefile b/Makefile index ac633a12ff..15f4706943 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ generate-external-crds: ## Generate CRDs for externally defined CRDs and copy th .PHONY: bats-tests bats-tests: ## Run Helm chart bats tests. - bats --jobs 4 charts/consul/test/unit + docker run -it -v $(CURDIR):/consul-k8s hashicorpdev/consul-helm-test:latest bats --jobs 4 /consul-k8s/charts/consul/test/unit -f "$(TEST_NAME)" ##@ Control Plane Targets diff --git a/acceptance/go.mod b/acceptance/go.mod index 7d78edc277..b64e1d4631 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -9,7 +9,7 @@ require ( github.com/google/uuid v1.3.0 github.com/gruntwork-io/terratest v0.46.7 github.com/hashicorp/consul-k8s/control-plane v0.0.0-20240821160356-557f7c37e108 - github.com/hashicorp/consul/api v1.29.4 + github.com/hashicorp/consul/api v1.30.0 github.com/hashicorp/consul/sdk v0.16.1 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-uuid v1.0.3 @@ -126,15 +126,15 @@ require ( go.opentelemetry.io/otel/metric v1.19.0 // indirect go.opentelemetry.io/otel/sdk v1.19.0 // indirect go.opentelemetry.io/otel/trace v1.19.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect + golang.org/x/net v0.34.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.24.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index 18b2c1bcf0..de4819c6a9 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -186,8 +186,8 @@ github.com/gruntwork-io/terratest v0.46.7 h1:oqGPBBO87SEsvBYaA0R5xOq+Lm2Xc5dmFVf github.com/gruntwork-io/terratest v0.46.7/go.mod h1:6gI5MlLeyF+SLwqocA5GBzcTix+XiuxCy1BPwKuT+WM= github.com/hashicorp/consul-k8s/control-plane v0.0.0-20240821160356-557f7c37e108 h1:5jSMtMGeY//hvkAefiomxP1Jqb5MtnKgsnlsZpEwiJE= github.com/hashicorp/consul-k8s/control-plane v0.0.0-20240821160356-557f7c37e108/go.mod h1:SY22WR9TJmlcK18Et2MAqy+kqAFJzbWFElN89vMTSiM= -github.com/hashicorp/consul/api v1.29.4 h1:P6slzxDLBOxUSj3fWo2o65VuKtbtOXFi7TSSgtXutuE= -github.com/hashicorp/consul/api v1.29.4/go.mod h1:HUlfw+l2Zy68ceJavv2zAyArl2fqhGWnMycyt56sBgg= +github.com/hashicorp/consul/api v1.30.0 h1:ArHVMMILb1nQv8vZSGIwwQd2gtc+oSQZ6CalyiyH2XQ= +github.com/hashicorp/consul/api v1.30.0/go.mod h1:B2uGchvaXVW2JhFoS8nqTxMD5PBykr4ebY4JWHTTeLM= github.com/hashicorp/consul/proto-public v0.6.2 h1:+DA/3g/IiKlJZb88NBn0ZgXrxJp2NlvCZdEyl+qxvL0= github.com/hashicorp/consul/proto-public v0.6.2/go.mod h1:cXXbOg74KBNGajC+o8RlA502Esf0R9prcoJgiOX/2Tg= github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg= @@ -469,8 +469,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -499,8 +499,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -513,8 +513,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -552,16 +552,16 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -572,8 +572,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index 3dc8d13015..3326312c1c 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -3,8 +3,8 @@ apiVersion: v2 name: consul -version: 1.6.0-dev -appVersion: 1.20-dev +version: 1.6.2 +appVersion: 1.20.2 kubeVersion: ">=1.22.0-0" description: Official HashiCorp Consul Chart home: https://www.consul.io @@ -13,14 +13,14 @@ sources: - https://github.com/hashicorp/consul - https://github.com/hashicorp/consul-k8s annotations: - artifacthub.io/prerelease: true + artifacthub.io/prerelease: false artifacthub.io/images: | - name: consul - image: docker.mirror.hashicorp.services/hashicorppreview/consul:1.20-dev + image: hashicorp/consul:1.20.2 - name: consul-k8s-control-plane - image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.6-dev + image: hashicorp/consul-k8s-control-plane:1.6.2 - name: consul-dataplane - image: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.6-dev + image: hashicorp/consul-dataplane:1.6.2 - name: envoy image: envoyproxy/envoy:v1.25.11 artifacthub.io/license: MPL-2.0 diff --git a/charts/consul/templates/connect-inject-configmap.yaml b/charts/consul/templates/connect-inject-configmap.yaml new file mode 100644 index 0000000000..98a7dae45f --- /dev/null +++ b/charts/consul/templates/connect-inject-configmap.yaml @@ -0,0 +1,18 @@ +{{- if .Values.connectInject.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "consul.fullname" . }}-connect-inject-config + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: connect-injector +data: + config.json: | + { + "image_pull_secrets": {{ .Values.global.imagePullSecrets | toJson }} + } +{{- end }} diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index 725c26df10..8eebcfe42f 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -141,6 +141,7 @@ spec: - "-ec" - | exec consul-k8s-control-plane inject-connect \ + -config-file=/consul/config/config.json \ {{- if .Values.global.federation.enabled }} -enable-federation \ {{- end }} @@ -311,6 +312,9 @@ spec: successThreshold: 1 timeoutSeconds: 5 volumeMounts: + - name: config + mountPath: /consul/config + readOnly: true {{- if not (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName) }} - name: certs mountPath: /etc/connect-injector/certs @@ -326,6 +330,9 @@ spec: {{- toYaml . | nindent 12 }} {{- end }} volumes: + - name: config + configMap: + name: {{ template "consul.fullname" . }}-connect-inject-config {{- if not (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName) }} - name: certs secret: diff --git a/charts/consul/templates/crd-meshes.yaml b/charts/consul/templates/crd-meshes.yaml index f81e61a2c5..9ecc1f3cbc 100644 --- a/charts/consul/templates/crd-meshes.yaml +++ b/charts/consul/templates/crd-meshes.yaml @@ -66,10 +66,55 @@ spec: http: description: HTTP defines the HTTP configuration for the service mesh. properties: + incoming: + description: Incoming configures settings for incoming HTTP traffic + to mesh proxies. + properties: + requestNormalization: + description: |- + RequestNormalizationMeshConfig contains options pertaining to the + normalization of HTTP requests processed by mesh proxies. + properties: + headersWithUnderscoresAction: + description: |- + HeadersWithUnderscoresAction sets the value of the \`headers_with_underscores_action\` option in the Envoy + listener's \`HttpConnectionManager\` under \`common_http_protocol_options\`. The default value of this option is + empty, which is equivalent to \`ALLOW\`. Refer to the Envoy documentation for more information on available + options. + type: string + insecureDisablePathNormalization: + description: |- + InsecureDisablePathNormalization sets the value of the \`normalize_path\` option in the Envoy listener's + `HttpConnectionManager`. The default value is \`false\`. When set to \`true\` in Consul, \`normalize_path\` is + set to \`false\` for the Envoy proxy. This parameter disables the normalization of request URL paths according to + RFC 3986, conversion of \`\\\` to \`/\`, and decoding non-reserved %-encoded characters. When using L7 intentions + with path match rules, we recommend enabling path normalization in order to avoid match rule circumvention with + non-normalized path values. + type: boolean + mergeSlashes: + description: |- + MergeSlashes sets the value of the \`merge_slashes\` option in the Envoy listener's \`HttpConnectionManager\`. + The default value is \`false\`. This option controls the normalization of request URL paths by merging + consecutive \`/\` characters. This normalization is not part of RFC 3986. When using L7 intentions with path + match rules, we recommend enabling this setting to avoid match rule circumvention through non-normalized path + values, unless legitimate service traffic depends on allowing for repeat \`/\` characters, or upstream services + are configured to differentiate between single and multiple slashes. + type: boolean + pathWithEscapedSlashesAction: + description: |- + PathWithEscapedSlashesAction sets the value of the \`path_with_escaped_slashes_action\` option in the Envoy + listener's \`HttpConnectionManager\`. The default value of this option is empty, which is equivalent to + \`IMPLEMENTATION_SPECIFIC_DEFAULT\`. This parameter controls the action taken in response to request URL paths + with escaped slashes in the path. When using L7 intentions with path match rules, we recommend enabling this + setting to avoid match rule circumvention through non-normalized path values, unless legitimate service traffic + depends on allowing for escaped \`/\` or \`\\\` characters, or upstream services are configured to differentiate + between escaped and unescaped slashes. Refer to the Envoy documentation for more information on available + options. + type: string + type: object + type: object sanitizeXForwardedClientCert: type: boolean - required: - - sanitizeXForwardedClientCert type: object peering: description: Peering defines the peering configuration for the service diff --git a/charts/consul/templates/crd-serviceintentions.yaml b/charts/consul/templates/crd-serviceintentions.yaml index 72159ec187..09daae2f6c 100644 --- a/charts/consul/templates/crd-serviceintentions.yaml +++ b/charts/consul/templates/crd-serviceintentions.yaml @@ -168,10 +168,19 @@ spec: If more than one is configured all must match for the overall match to apply. items: properties: + contains: + description: Contains matches if the header + with the given name contains this value. + type: string exact: description: Exact matches if the header with the given name is this value. type: string + ignoreCase: + description: IgnoreCase ignores the case of + the header value when matching with exact, + prefix, suffix, or contains. + type: boolean invert: description: Invert inverts the logic of the match. diff --git a/charts/consul/templates/gateway-resources-job.yaml b/charts/consul/templates/gateway-resources-job.yaml index 5f3110479c..ff53129eb3 100644 --- a/charts/consul/templates/gateway-resources-job.yaml +++ b/charts/consul/templates/gateway-resources-job.yaml @@ -69,7 +69,8 @@ spec: - {{- toYaml .Values.connectInject.apiGateway.managedGatewayClass.nodeSelector | nindent 14 -}} {{- end }} {{- if .Values.connectInject.apiGateway.managedGatewayClass.tolerations }} - - -tolerations={{ .Values.connectInject.apiGateway.managedGatewayClass.tolerations }} + - -tolerations + - {{- toYaml .Values.connectInject.apiGateway.managedGatewayClass.tolerations | nindent 14 -}} {{- end }} {{- if .Values.connectInject.apiGateway.managedGatewayClass.copyAnnotations.service }} - -service-annotations diff --git a/charts/consul/templates/sync-catalog-cleanup-on-uninstall-job.yaml b/charts/consul/templates/sync-catalog-cleanup-on-uninstall-job.yaml new file mode 100644 index 0000000000..955042dd0a --- /dev/null +++ b/charts/consul/templates/sync-catalog-cleanup-on-uninstall-job.yaml @@ -0,0 +1,174 @@ +{{- if .Values.syncCatalog.cleanupNodeOnRemoval }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ template "consul.fullname" . }}-sync-catalog-cleanup-on-uninstall + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: sync-catalog-cleanup + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} + annotations: + "helm.sh/hook": pre-delete + "helm.sh/hook-weight": "0" + "helm.sh/hook-delete-policy": hook-succeeded,hook-failed +spec: + template: + metadata: + name: {{ template "consul.fullname" . }}-sync-catalog-cleanup-on-uninstall + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + release: {{ .Release.Name }} + component: sync-catalog-cleanup + {{- if .Values.syncCatalog.extraLabels }} + {{- toYaml .Values.syncCatalog.extraLabels | nindent 8 }} + {{- end }} + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} + annotations: + "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" + {{- if .Values.syncCatalog.annotations }} + {{- tpl .Values.syncCatalog.annotations . | nindent 8 }} + {{- end }} + {{- if (and .Values.global.secretsBackend.vault.enabled .Values.global.tls.enabled) }} + "vault.hashicorp.com/agent-init-first": "true" + "vault.hashicorp.com/agent-inject": "true" + "vault.hashicorp.com/role": {{ .Values.global.secretsBackend.vault.consulCARole }} + "vault.hashicorp.com/agent-inject-secret-serverca.crt": {{ .Values.global.tls.caCert.secretName }} + "vault.hashicorp.com/agent-inject-template-serverca.crt": {{ template "consul.serverTLSCATemplate" . }} + {{- if and .Values.global.secretsBackend.vault.ca.secretName .Values.global.secretsBackend.vault.ca.secretKey }} + "vault.hashicorp.com/agent-extra-secret": "{{ .Values.global.secretsBackend.vault.ca.secretName }}" + "vault.hashicorp.com/ca-cert": "/vault/custom/{{ .Values.global.secretsBackend.vault.ca.secretKey }}" + {{- end }} + {{- if .Values.global.secretsBackend.vault.agentAnnotations }} + {{ tpl .Values.global.secretsBackend.vault.agentAnnotations . | nindent 8 | trim }} + {{- end }} + {{- if (and (.Values.global.secretsBackend.vault.vaultNamespace) (not (hasKey (default "" .Values.global.secretsBackend.vault.agentAnnotations | fromYaml) "vault.hashicorp.com/namespace")))}} + "vault.hashicorp.com/namespace": "{{ .Values.global.secretsBackend.vault.vaultNamespace }}" + {{- end }} + {{- end }} + {{- if .Values.syncCatalog.metrics.enabled | default .Values.global.metrics.enabled }} + "prometheus.io/scrape": "true" + {{- if not (hasKey (default "" .Values.syncCatalog.annotations | fromYaml) "prometheus.io/path")}} + "prometheus.io/path": {{ default "/metrics" .Values.syncCatalog.metrics.path }} + {{- end }} + "prometheus.io/port": {{ .Values.syncCatalog.metrics.port | default "20300" | quote }} + {{- end }} + spec: + restartPolicy: Never + serviceAccountName: {{ template "consul.fullname" . }}-sync-catalog-cleanup + containers: + - name: sync-catalog-cleanup-job + image: "{{ default .Values.global.imageK8S .Values.syncCatalog.image }}" + {{ template "consul.imagePullPolicy" . }} + {{- include "consul.restrictedSecurityContext" . | nindent 8 }} + env: + {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 8 }} + {{- if .Values.global.acls.manageSystemACLs }} + - name: CONSUL_LOGIN_AUTH_METHOD + {{- if and .Values.global.federation.enabled .Values.global.federation.primaryDatacenter .Values.global.enableConsulNamespaces }} + value: {{ template "consul.fullname" . }}-k8s-component-auth-method-{{ .Values.global.datacenter }} + {{- else }} + value: {{ template "consul.fullname" . }}-k8s-component-auth-method + {{- end }} + - name: CONSUL_LOGIN_DATACENTER + {{- if and .Values.global.federation.enabled .Values.global.federation.primaryDatacenter .Values.global.enableConsulNamespaces }} + value: {{ .Values.global.federation.primaryDatacenter }} + {{- else }} + value: {{ .Values.global.datacenter }} + {{- end }} + - name: CONSUL_LOGIN_META + value: "component=sync-catalog,pod=$(NAMESPACE)/$(POD_NAME)" + {{- end }} + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- if (and .Values.syncCatalog.aclSyncToken.secretName .Values.syncCatalog.aclSyncToken.secretKey) }} + - name: CONSUL_ACL_TOKEN + valueFrom: + secretKeyRef: + name: {{ .Values.syncCatalog.aclSyncToken.secretName }} + key: {{ .Values.syncCatalog.aclSyncToken.secretKey }} + {{- end }} + volumeMounts: + {{- if .Values.global.tls.enabled }} + {{- if not (or (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) .Values.global.secretsBackend.vault.enabled) }} + - name: consul-ca-cert + mountPath: /consul/tls/ca + readOnly: true + {{- end }} + {{- end }} + command: + - "/bin/sh" + - "-ec" + - | + exec consul-k8s-control-plane sync-catalog \ + -log-level={{ default .Values.global.logLevel .Values.syncCatalog.logLevel }} \ + -log-json={{ .Values.global.logJSON }} \ + -k8s-default-sync={{ .Values.syncCatalog.default }} \ + {{- if .Values.global.adminPartitions.enabled }} + -partition={{ .Values.global.adminPartitions.name }} \ + {{- end }} + {{- if .Values.syncCatalog.consulNodeName }} + -consul-node-name={{ .Values.syncCatalog.consulNodeName }} \ + {{- end }} + {{- if .Values.syncCatalog.metrics.enabled | default .Values.global.metrics.enabled }} + -enable-metrics \ + {{- end }} + {{- if .Values.syncCatalog.metrics.path }} + -metrics-path={{ .Values.syncCatalog.metrics.path }} \ + {{- end }} + {{- if .Values.syncCatalog.metrics.port }} + -metrics-port={{ .Values.syncCatalog.metrics.port }} \ + {{- end }} + -prometheus-retention-time={{ .Values.global.metrics.agentMetricsRetentionTime }} \ + -purge-k8s-services-from-node + {{- with .Values.syncCatalog.resources }} + resources: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- if or (eq (.Values.syncCatalog.metrics.enabled | toString) "-") .Values.syncCatalog.metrics.enabled .Values.global.metrics.enabled }} + ports: + - name: prometheus + containerPort: {{ .Values.syncCatalog.metrics.port | default "20300" | int }} + {{- end }} + {{- if .Values.syncCatalog.priorityClassName }} + priorityClassName: {{ .Values.syncCatalog.priorityClassName | quote }} + {{- end }} + {{- if .Values.syncCatalog.nodeSelector }} + nodeSelector: + {{ tpl .Values.syncCatalog.nodeSelector . | indent 8 | trim }} + {{- end }} + {{- if .Values.syncCatalog.affinity }} + affinity: + {{ tpl .Values.syncCatalog.affinity . | indent 8 | trim }} + {{- end }} + {{- if .Values.syncCatalog.tolerations }} + tolerations: + {{ tpl .Values.syncCatalog.tolerations . | indent 8 | trim }} + {{- end }} + volumes: + {{- if .Values.global.tls.enabled }} + {{- if not (or (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) .Values.global.secretsBackend.vault.enabled) }} + - name: consul-ca-cert + secret: + {{- if .Values.global.tls.caCert.secretName }} + secretName: {{ .Values.global.tls.caCert.secretName }} + {{- else }} + secretName: {{ template "consul.fullname" . }}-ca-cert + {{- end }} + items: + - key: {{ default "tls.crt" .Values.global.tls.caCert.secretKey }} + path: tls.crt + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/consul/templates/sync-catalog-cleanup-on-upgrade-job.yaml b/charts/consul/templates/sync-catalog-cleanup-on-upgrade-job.yaml new file mode 100644 index 0000000000..2349c513d4 --- /dev/null +++ b/charts/consul/templates/sync-catalog-cleanup-on-upgrade-job.yaml @@ -0,0 +1,175 @@ +{{- $syncCatalogEnabled := (or (and (ne (.Values.syncCatalog.enabled | toString) "-") .Values.syncCatalog.enabled) (and (eq (.Values.syncCatalog.enabled | toString) "-") .Values.global.enabled)) }} +{{- if and (not $syncCatalogEnabled) .Values.syncCatalog.cleanupNodeOnRemoval .Release.IsUpgrade }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ template "consul.fullname" . }}-sync-catalog-cleanup-on-upgrade + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: sync-catalog-cleanup + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 4 }} + {{- end }} + annotations: + "helm.sh/hook": pre-upgrade + "helm.sh/hook-weight": "0" + "helm.sh/hook-delete-policy": hook-succeeded,hook-failed +spec: + template: + metadata: + name: {{ template "consul.fullname" . }}-sync-catalog-cleanup-on-upgrade + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + release: {{ .Release.Name }} + component: sync-catalog-cleanup + {{- if .Values.syncCatalog.extraLabels }} + {{- toYaml .Values.syncCatalog.extraLabels | nindent 8 }} + {{- end }} + {{- if .Values.global.extraLabels }} + {{- toYaml .Values.global.extraLabels | nindent 8 }} + {{- end }} + annotations: + "consul.hashicorp.com/connect-inject": "false" + "consul.hashicorp.com/mesh-inject": "false" + {{- if .Values.syncCatalog.annotations }} + {{- tpl .Values.syncCatalog.annotations . | nindent 8 }} + {{- end }} + {{- if (and .Values.global.secretsBackend.vault.enabled .Values.global.tls.enabled) }} + "vault.hashicorp.com/agent-init-first": "true" + "vault.hashicorp.com/agent-inject": "true" + "vault.hashicorp.com/role": {{ .Values.global.secretsBackend.vault.consulCARole }} + "vault.hashicorp.com/agent-inject-secret-serverca.crt": {{ .Values.global.tls.caCert.secretName }} + "vault.hashicorp.com/agent-inject-template-serverca.crt": {{ template "consul.serverTLSCATemplate" . }} + {{- if and .Values.global.secretsBackend.vault.ca.secretName .Values.global.secretsBackend.vault.ca.secretKey }} + "vault.hashicorp.com/agent-extra-secret": "{{ .Values.global.secretsBackend.vault.ca.secretName }}" + "vault.hashicorp.com/ca-cert": "/vault/custom/{{ .Values.global.secretsBackend.vault.ca.secretKey }}" + {{- end }} + {{- if .Values.global.secretsBackend.vault.agentAnnotations }} + {{ tpl .Values.global.secretsBackend.vault.agentAnnotations . | nindent 8 | trim }} + {{- end }} + {{- if (and (.Values.global.secretsBackend.vault.vaultNamespace) (not (hasKey (default "" .Values.global.secretsBackend.vault.agentAnnotations | fromYaml) "vault.hashicorp.com/namespace")))}} + "vault.hashicorp.com/namespace": "{{ .Values.global.secretsBackend.vault.vaultNamespace }}" + {{- end }} + {{- end }} + {{- if .Values.syncCatalog.metrics.enabled | default .Values.global.metrics.enabled }} + "prometheus.io/scrape": "true" + {{- if not (hasKey (default "" .Values.syncCatalog.annotations | fromYaml) "prometheus.io/path")}} + "prometheus.io/path": {{ default "/metrics" .Values.syncCatalog.metrics.path }} + {{- end }} + "prometheus.io/port": {{ .Values.syncCatalog.metrics.port | default "20300" | quote }} + {{- end }} + spec: + restartPolicy: Never + serviceAccountName: {{ template "consul.fullname" . }}-sync-catalog-cleanup + containers: + - name: sync-catalog-cleanup-job + image: "{{ default .Values.global.imageK8S .Values.syncCatalog.image }}" + {{ template "consul.imagePullPolicy" . }} + {{- include "consul.restrictedSecurityContext" . | nindent 8 }} + env: + {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 8 }} + {{- if .Values.global.acls.manageSystemACLs }} + - name: CONSUL_LOGIN_AUTH_METHOD + {{- if and .Values.global.federation.enabled .Values.global.federation.primaryDatacenter .Values.global.enableConsulNamespaces }} + value: {{ template "consul.fullname" . }}-k8s-component-auth-method-{{ .Values.global.datacenter }} + {{- else }} + value: {{ template "consul.fullname" . }}-k8s-component-auth-method + {{- end }} + - name: CONSUL_LOGIN_DATACENTER + {{- if and .Values.global.federation.enabled .Values.global.federation.primaryDatacenter .Values.global.enableConsulNamespaces }} + value: {{ .Values.global.federation.primaryDatacenter }} + {{- else }} + value: {{ .Values.global.datacenter }} + {{- end }} + - name: CONSUL_LOGIN_META + value: "component=sync-catalog,pod=$(NAMESPACE)/$(POD_NAME)" + {{- end }} + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- if (and .Values.syncCatalog.aclSyncToken.secretName .Values.syncCatalog.aclSyncToken.secretKey) }} + - name: CONSUL_ACL_TOKEN + valueFrom: + secretKeyRef: + name: {{ .Values.syncCatalog.aclSyncToken.secretName }} + key: {{ .Values.syncCatalog.aclSyncToken.secretKey }} + {{- end }} + volumeMounts: + {{- if .Values.global.tls.enabled }} + {{- if not (or (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) .Values.global.secretsBackend.vault.enabled) }} + - name: consul-ca-cert + mountPath: /consul/tls/ca + readOnly: true + {{- end }} + {{- end }} + command: + - "/bin/sh" + - "-ec" + - | + exec consul-k8s-control-plane sync-catalog \ + -log-level={{ default .Values.global.logLevel .Values.syncCatalog.logLevel }} \ + -log-json={{ .Values.global.logJSON }} \ + -k8s-default-sync={{ .Values.syncCatalog.default }} \ + {{- if .Values.global.adminPartitions.enabled }} + -partition={{ .Values.global.adminPartitions.name }} \ + {{- end }} + {{- if .Values.syncCatalog.consulNodeName }} + -consul-node-name={{ .Values.syncCatalog.consulNodeName }} \ + {{- end }} + {{- if .Values.syncCatalog.metrics.enabled | default .Values.global.metrics.enabled }} + -enable-metrics \ + {{- end }} + {{- if .Values.syncCatalog.metrics.path }} + -metrics-path={{ .Values.syncCatalog.metrics.path }} \ + {{- end }} + {{- if .Values.syncCatalog.metrics.port }} + -metrics-port={{ .Values.syncCatalog.metrics.port }} \ + {{- end }} + -prometheus-retention-time={{ .Values.global.metrics.agentMetricsRetentionTime }} \ + -purge-k8s-services-from-node + {{- with .Values.syncCatalog.resources }} + resources: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- if or (eq (.Values.syncCatalog.metrics.enabled | toString) "-") .Values.syncCatalog.metrics.enabled .Values.global.metrics.enabled }} + ports: + - name: prometheus + containerPort: {{ .Values.syncCatalog.metrics.port | default "20300" | int }} + {{- end }} + {{- if .Values.syncCatalog.priorityClassName }} + priorityClassName: {{ .Values.syncCatalog.priorityClassName | quote }} + {{- end }} + {{- if .Values.syncCatalog.nodeSelector }} + nodeSelector: + {{ tpl .Values.syncCatalog.nodeSelector . | indent 8 | trim }} + {{- end }} + {{- if .Values.syncCatalog.affinity }} + affinity: + {{ tpl .Values.syncCatalog.affinity . | indent 8 | trim }} + {{- end }} + {{- if .Values.syncCatalog.tolerations }} + tolerations: + {{ tpl .Values.syncCatalog.tolerations . | indent 8 | trim }} + {{- end }} + volumes: + {{- if .Values.global.tls.enabled }} + {{- if not (or (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) .Values.global.secretsBackend.vault.enabled) }} + - name: consul-ca-cert + secret: + {{- if .Values.global.tls.caCert.secretName }} + secretName: {{ .Values.global.tls.caCert.secretName }} + {{- else }} + secretName: {{ template "consul.fullname" . }}-ca-cert + {{- end }} + items: + - key: {{ default "tls.crt" .Values.global.tls.caCert.secretKey }} + path: tls.crt + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/consul/templates/sync-catalog-cleanup-serviceaccount.yaml b/charts/consul/templates/sync-catalog-cleanup-serviceaccount.yaml new file mode 100644 index 0000000000..c49852b14c --- /dev/null +++ b/charts/consul/templates/sync-catalog-cleanup-serviceaccount.yaml @@ -0,0 +1,23 @@ +{{- if .Values.syncCatalog.cleanupNodeOnRemoval }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "consul.fullname" . }}-sync-catalog-cleanup + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: sync-catalog-cleanup + {{- if .Values.syncCatalog.serviceAccount.annotations }} + annotations: + {{ tpl .Values.syncCatalog.serviceAccount.annotations . | nindent 4 | trim }} + {{- end }} +{{- with .Values.global.imagePullSecrets }} +imagePullSecrets: +{{- range . }} + - name: {{ .name }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/consul/templates/sync-catalog-deployment.yaml b/charts/consul/templates/sync-catalog-deployment.yaml index 94260b5e44..5e69110810 100644 --- a/charts/consul/templates/sync-catalog-deployment.yaml +++ b/charts/consul/templates/sync-catalog-deployment.yaml @@ -1,4 +1,5 @@ -{{- if (or (and (ne (.Values.syncCatalog.enabled | toString) "-") .Values.syncCatalog.enabled) (and (eq (.Values.syncCatalog.enabled | toString) "-") .Values.global.enabled)) }} +{{- $syncCatalogEnabled := (or (and (ne (.Values.syncCatalog.enabled | toString) "-") .Values.syncCatalog.enabled) (and (eq (.Values.syncCatalog.enabled | toString) "-") .Values.global.enabled)) }} +{{- if $syncCatalogEnabled }} {{- template "consul.reservedNamesFailer" (list .Values.syncCatalog.consulNamespaces.consulDestinationNamespace "syncCatalog.consulNamespaces.consulDestinationNamespace") }} {{ template "consul.validateRequiredCloudSecretsExist" . }} {{ template "consul.validateCloudSecretKeys" . }} diff --git a/charts/consul/test/docker/Test.dockerfile b/charts/consul/test/docker/Test.dockerfile index e6a4caa6e0..e8b00687b3 100644 --- a/charts/consul/test/docker/Test.dockerfile +++ b/charts/consul/test/docker/Test.dockerfile @@ -9,50 +9,50 @@ # a script to configure kubectl, potentially install Helm, and run the tests # manually. This image only has the dependencies pre-installed. -FROM cimg/go:1.19 +FROM cimg/go:1.23.1 # change the user to root so we can install stuff USER root -ENV BATS_VERSION "1.6.0" -ENV TERRAFORM_VERSION "0.13.5" +ENV BATS_VERSION "1.11.0" +ENV TERRAFORM_VERSION "1.9.6" RUN apt-get update # base packages RUN apt-get install -y \ - openssl \ - python3 \ - python3-pip \ - jq + openssl \ + python3 \ + python3-pip \ + jq # yq RUN pip3 install yq # gcloud RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list && \ - curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - && \ - apt-get update -y && \ - apt-get install google-cloud-sdk -y && \ - apt-get install google-cloud-sdk-gke-gcloud-auth-plugin + curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - && \ + apt-get update -y && \ + apt-get install google-cloud-sdk -y && \ + apt-get install google-cloud-sdk-gke-gcloud-auth-plugin # terraform RUN curl -sSL https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip -o /tmp/tf.zip \ - && unzip /tmp/tf.zip \ - && mv ./terraform /usr/local/bin/terraform + && unzip /tmp/tf.zip \ + && mv ./terraform /usr/local/bin/terraform # kubectl RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && \ - chmod +x ./kubectl && \ - mv ./kubectl /usr/local/bin/kubectl + chmod +x ./kubectl && \ + mv ./kubectl /usr/local/bin/kubectl # helm RUN curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash # bats RUN curl -sSL https://github.com/bats-core/bats-core/archive/v${BATS_VERSION}.tar.gz -o /tmp/bats.tgz \ - && tar -zxf /tmp/bats.tgz -C /tmp \ - && /bin/bash /tmp/bats-core-${BATS_VERSION}/install.sh /usr/local + && tar -zxf /tmp/bats.tgz -C /tmp \ + && /bin/bash /tmp/bats-core-${BATS_VERSION}/install.sh /usr/local # Azure CLI RUN curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash @@ -60,20 +60,20 @@ RUN curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash # OpenShift CLI # https://docs.microsoft.com/en-us/azure/openshift/tutorial-connect-cluster RUN curl -sSL https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest/openshift-client-linux.tar.gz -o /tmp/oc.tar.gz \ - && tar -zxvf /tmp/oc.tar.gz -C /tmp \ - && mv /tmp/oc /usr/local/bin/oc + && tar -zxvf /tmp/oc.tar.gz -C /tmp \ + && mv /tmp/oc /usr/local/bin/oc # AWS CLI RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" \ - && unzip awscliv2.zip \ - && ./aws/install --bin-dir /usr/local/bin \ - && rm awscliv2.zip \ - && rm -rf ./aws + && unzip awscliv2.zip \ + && ./aws/install --bin-dir /usr/local/bin \ + && rm awscliv2.zip \ + && rm -rf ./aws # AWS IAM authenticator RUN curl -Lo aws-iam-authenticator https://github.com/kubernetes-sigs/aws-iam-authenticator/releases/download/v0.5.9/aws-iam-authenticator_0.5.9_linux_amd64 \ - && chmod +x ./aws-iam-authenticator \ - && mv ./aws-iam-authenticator /usr/local/bin/aws-iam-authenticator + && chmod +x ./aws-iam-authenticator \ + && mv ./aws-iam-authenticator /usr/local/bin/aws-iam-authenticator # change the user back to what circleci/golang image has USER circleci diff --git a/charts/consul/test/unit/gateway-resources-job.bats b/charts/consul/test/unit/gateway-resources-job.bats index 32173838fe..fd64acac02 100644 --- a/charts/consul/test/unit/gateway-resources-job.bats +++ b/charts/consul/test/unit/gateway-resources-job.bats @@ -71,7 +71,6 @@ target=templates/gateway-resources-job.yaml --set 'connectInject.apiGateway.managedGatewayClass.deployment.minInstances=1' \ --set 'connectInject.apiGateway.managedGatewayClass.deployment.maxInstances=3' \ --set 'connectInject.apiGateway.managedGatewayClass.nodeSelector=foo: bar' \ - --set 'connectInject.apiGateway.managedGatewayClass.tolerations=- key: bar' \ --set 'connectInject.apiGateway.managedGatewayClass.copyAnnotations.service.annotations=- bingo' \ --set 'connectInject.apiGateway.managedGatewayClass.serviceType=Foo' \ --set 'connectInject.apiGateway.managedGatewayClass.openshiftSCCName=hello' \ @@ -90,23 +89,11 @@ target=templates/gateway-resources-job.yaml local actual=$(echo "$spec" | jq 'any(index("-service-type=Foo"))') [ "${actual}" = "true" ] - local actual=$(echo "$spec" | jq '.[12]') - [ "${actual}" = "\"-node-selector\"" ] - - local actual=$(echo "$spec" | jq '.[13]') - [ "${actual}" = "\"foo: bar\"" ] - - local actual=$(echo "$spec" | jq '.[14] | ."-tolerations=- key"') - [ "${actual}" = "\"bar\"" ] - - local actual=$(echo "$spec" | jq '.[15]') - [ "${actual}" = "\"-service-annotations\"" ] - - local actual=$(echo "$spec" | jq '.[16]') - [ "${actual}" = "\"- bingo\"" ] + local actual=$(echo $spec | yq 'contains(["-node-selector", "foo: bar"])') + [ "${actual}" = "true" ] - local actual=$(echo "$spec" | jq '.[17]') - [ "${actual}" = "\"-service-type=Foo\"" ] + local actual=$(echo $spec | yq 'contains(["-service-annotations", "- bingo"])') + [ "${actual}" = "true" ] } @test "apiGateway/GatewayClassConfig: custom configuration openshift enabled" { @@ -138,3 +125,26 @@ target=templates/gateway-resources-job.yaml tee /dev/stderr) [ "${actual}" = "{}" ] } + + +#-------------------------------------------------------------------- +# tolerations + +@test "apiGateway/GatewayClassConfig: tolerations" { + cd `chart_dir` + local tolerations=$(helm template \ + -s $target \ + --set 'connectInject.apiGateway.managedGatewayClass.tolerations=- "operator": "Equal" \ +"effect": "NoSchedule" \ +"key": "node" \ +"value": "clients" \ +- "operator": "Equal" \ +"effect": "NoSchedule" \ +"key": "node2" \ +"value": "clients2"' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].args' | tee /dev/stderr) + + local actual=$(echo $tolerations | yq 'contains(["tolerations","- \"operator\": \"Equal\" \n\"effect\": \"NoSchedule\" \n\"key\": \"node\" \n\"value\": \"clients\" \n- \"operator\": \"Equal\" \n\"effect\": \"NoSchedule\" \n\"key\": \"node2\" \n\"value\": \"clients2\"" ])') + [ "${actual}" = "true" ] +} \ No newline at end of file diff --git a/charts/consul/test/unit/sync-catalog-cleanup-on-uninstall-job.bats b/charts/consul/test/unit/sync-catalog-cleanup-on-uninstall-job.bats new file mode 100644 index 0000000000..f7f4935ec3 --- /dev/null +++ b/charts/consul/test/unit/sync-catalog-cleanup-on-uninstall-job.bats @@ -0,0 +1,839 @@ +#!/usr/bin/env bats + +load _helpers + +target=templates/sync-catalog-cleanup-on-uninstall-job.yaml + +@test "syncCatalogCleanupJob/Uninstall: disabled by default" { + cd $(chart_dir) + assert_empty helm template \ + -s $target \ + . +} + +@test "syncCatalogCleanupJob/Uninstall: enable with syncCatalog.cleanupNodeOnRemoval true" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq -s 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# image + +@test "syncCatalogCleanupJob/Uninstall: image defaults to global.imageK8S" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'global.imageK8S=bar' \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].image' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} + +@test "syncCatalogCleanupJob/Uninstall: image can be overridden with server.image" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'global.imageK8S=foo' \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.image=bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].image' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} + +@test "syncCatalogCleanupJob/Uninstall: consul env defaults" { + cd $(chart_dir) + local env=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[]' | tee /dev/stderr) + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_ADDRESSES").value' | tee /dev/stderr) + [ "${actual}" = "release-name-consul-server.default.svc" ] + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_GRPC_PORT").value' | tee /dev/stderr) + [ "${actual}" = "8502" ] + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_HTTP_PORT").value' | tee /dev/stderr) + [ "${actual}" = "8500" ] + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_DATACENTER").value' | tee /dev/stderr) + [ "${actual}" = "dc1" ] + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_API_TIMEOUT").value' | tee /dev/stderr) + [ "${actual}" = "5s" ] +} + +#-------------------------------------------------------------------- +# consulNodeName + +@test "syncCatalogCleanupJob/Uninstall: consulNodeName defaults to k8s-sync" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-consul-node-name=k8s-sync"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "syncCatalogCleanupJob/Uninstall: consulNodeName set to empty" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.consulNodeName=' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-consul-node-name"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "syncCatalogCleanupJob/Uninstall: can specify consulNodeName" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.consulNodeName=aNodeName' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-consul-node-name=aNodeName"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# serviceAccount + +@test "syncCatalogCleanupJob/Uninstall: serviceAccount set when sync enabled" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.serviceAccountName | contains("sync-catalog-cleanup")' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# aclSyncToken + +@test "syncCatalogCleanupJob/Uninstall: aclSyncToken disabled when secretName is missing" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.aclSyncToken.secretKey=bar' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].env[].name] | any(contains("CONSUL_ACL_TOKEN"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "syncCatalogCleanupJob/Uninstall: aclSyncToken disabled when secretKey is missing" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.aclSyncToken.secretName=foo' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].env[].name] | any(contains("CONSUL_ACL_TOKEN"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "syncCatalogCleanupJob/Uninstall: aclSyncToken enabled when secretName and secretKey is provided" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.aclSyncToken.secretName=foo' \ + --set 'syncCatalog.aclSyncToken.secretKey=bar' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].env[].name] | any(contains("CONSUL_ACL_TOKEN"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# affinity + +@test "syncCatalogCleanupJob/Uninstall: affinity not set by default" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.affinity == null' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "syncCatalogCleanupJob/Uninstall: affinity can be set" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.affinity=foobar' \ + . | tee /dev/stderr | + yq '.spec.template.spec | .affinity == "foobar"' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# nodeSelector + +@test "syncCatalogCleanupJob/Uninstall: nodeSelector is not set by default" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.nodeSelector' | tee /dev/stderr) + [ "${actual}" = "null" ] +} + +@test "syncCatalogCleanupJob/Uninstall: nodeSelector is not set by default with sync enabled" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.nodeSelector' | tee /dev/stderr) + [ "${actual}" = "null" ] +} + +@test "syncCatalogCleanupJob/Uninstall: specified nodeSelector" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.nodeSelector=testing' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.nodeSelector' | tee /dev/stderr) + [ "${actual}" = "testing" ] +} + +#-------------------------------------------------------------------- +# tolerations + +@test "syncCatalogCleanupJob/Uninstall: tolerations not set by default" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.tolerations == null' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "syncCatalogCleanupJob/Uninstall: tolerations can be set" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.tolerations=foobar' \ + . | tee /dev/stderr | + yq '.spec.template.spec | .tolerations == "foobar"' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# global.acls.manageSystemACLs + +@test "syncCatalogCleanupJob/Uninstall: ACL auth method env vars are set when acls are enabled" { + cd $(chart_dir) + local env=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[]' | tee /dev/stderr) + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_LOGIN_AUTH_METHOD").value' | tee /dev/stderr) + [ "${actual}" = "release-name-consul-k8s-component-auth-method" ] + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_LOGIN_DATACENTER").value' | tee /dev/stderr) + [ "${actual}" = "dc1" ] + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_LOGIN_META").value' | tee /dev/stderr) + [ "${actual}" = 'component=sync-catalog,pod=$(NAMESPACE)/$(POD_NAME)' ] +} + +@test "syncCatalogCleanupJob/Uninstall: sets global auth method and primary datacenter when federation and acls and namespaces are enabled" { + cd $(chart_dir) + local env=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.federation.enabled=true' \ + --set 'global.federation.primaryDatacenter=dc1' \ + --set 'global.datacenter=dc2' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'global.tls.enabled=true' \ + --set 'meshGateway.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[]' | tee /dev/stderr) + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_LOGIN_AUTH_METHOD").value' | tee /dev/stderr) + [ "${actual}" = "release-name-consul-k8s-component-auth-method-dc2" ] + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_LOGIN_DATACENTER").value' | tee /dev/stderr) + [ "${actual}" = "dc1" ] +} + +@test "syncCatalogCleanupJob/Uninstall: sets default login partition and acls and partitions are enabled" { + cd $(chart_dir) + local env=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enableConsulNamespaces=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[]' | tee /dev/stderr) + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_LOGIN_PARTITION").value' | tee /dev/stderr) + [ "${actual}" = "default" ] +} + +@test "syncCatalogCleanupJob/Uninstall: sets non-default login partition and acls and partitions are enabled" { + cd $(chart_dir) + local env=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.adminPartitions.name=foo' \ + --set 'global.enableConsulNamespaces=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[]' | tee /dev/stderr) + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_LOGIN_PARTITION").value' | tee /dev/stderr) + [ "${actual}" = "foo" ] +} + +#-------------------------------------------------------------------- +# global.tls.enabled + +@test "syncCatalogCleanupJob/Uninstall: sets Consul environment variables when global.tls.enabled" { + cd $(chart_dir) + local env=$(helm template \ + -s $target \ + --set 'client.enabled=true' \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].env[]' | tee /dev/stderr) + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_HTTP_PORT").value' | tee /dev/stderr) + [ "${actual}" = "8501" ] + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_USE_TLS").value' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_CACERT_FILE").value' | tee /dev/stderr) + [ "${actual}" = "/consul/tls/ca/tls.crt" ] +} + +@test "syncCatalogCleanupJob/Uninstall: can overwrite CA secret with the provided one" { + cd $(chart_dir) + local ca_cert_volume=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo-ca-cert' \ + --set 'global.tls.caCert.secretKey=key' \ + --set 'global.tls.caKey.secretName=foo-ca-key' \ + --set 'global.tls.caKey.secretKey=key' \ + . | tee /dev/stderr | + yq '.spec.template.spec.volumes[] | select(.name=="consul-ca-cert")' | tee /dev/stderr) + + # check that the provided ca cert secret is attached as a volume + local actual + actual=$(echo $ca_cert_volume | jq -r '.secret.secretName' | tee /dev/stderr) + [ "${actual}" = "foo-ca-cert" ] + + # check that the volume uses the provided secret key + actual=$(echo $ca_cert_volume | jq -r '.secret.items[0].key' | tee /dev/stderr) + [ "${actual}" = "key" ] +} + +@test "syncCatalogCleanupJob/Uninstall: consul-ca-cert volumeMount is added when TLS is enabled" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "consul-ca-cert") | length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "syncCatalogCleanupJob/Uninstall: consul-ca-cert volume is not added if externalServers.enabled=true and externalServers.useSystemRoots=true" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=foo.com' \ + --set 'externalServers.useSystemRoots=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.volumes[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" = "" ] +} + +#-------------------------------------------------------------------- +# resources + +@test "syncCatalogCleanupJob/Uninstall: default resources" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq -rc '.spec.template.spec.containers[0].resources' | tee /dev/stderr) + [ "${actual}" = '{"limits":{"cpu":"50m","memory":"50Mi"},"requests":{"cpu":"50m","memory":"50Mi"}}' ] +} + +@test "syncCatalogCleanupJob/Uninstall: can set resources" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.resources.requests.memory=100Mi' \ + --set 'syncCatalog.resources.requests.cpu=100m' \ + --set 'syncCatalog.resources.limits.memory=200Mi' \ + --set 'syncCatalog.resources.limits.cpu=200m' \ + . | tee /dev/stderr | + yq -rc '.spec.template.spec.containers[0].resources' | tee /dev/stderr) + [ "${actual}" = '{"limits":{"cpu":"200m","memory":"200Mi"},"requests":{"cpu":"100m","memory":"100Mi"}}' ] +} + +#-------------------------------------------------------------------- +# extraLabels + +@test "syncCatalogCleanupJob/Uninstall: no extra labels defined by default" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "syncCatalogCleanupJob/Uninstall: can set extra labels" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.extraLabels.foo=bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels.foo' | tee /dev/stderr) + + [ "${actual}" = "bar" ] +} + +@test "syncCatalogCleanupJob/Uninstall: extra global labels can be set" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.extraLabels.foo=bar' \ + . | tee /dev/stderr) + local actualBar=$(echo "${actual}" | yq -r '.metadata.labels.foo' | tee /dev/stderr) + [ "${actualBar}" = "bar" ] + local actualTemplateBar=$(echo "${actual}" | yq -r '.spec.template.metadata.labels.foo' | tee /dev/stderr) + [ "${actualTemplateBar}" = "bar" ] +} + +@test "syncCatalogCleanupJob/Uninstall: multiple extra global labels can be set" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.extraLabels.foo=bar' \ + --set 'global.extraLabels.baz=qux' \ + . | tee /dev/stderr) + local actualFoo=$(echo "${actual}" | yq -r '.metadata.labels.foo' | tee /dev/stderr) + local actualBaz=$(echo "${actual}" | yq -r '.metadata.labels.baz' | tee /dev/stderr) + [ "${actualFoo}" = "bar" ] + [ "${actualBaz}" = "qux" ] + local actualTemplateFoo=$(echo "${actual}" | yq -r '.spec.template.metadata.labels.foo' | tee /dev/stderr) + local actualTemplateBaz=$(echo "${actual}" | yq -r '.spec.template.metadata.labels.baz' | tee /dev/stderr) + [ "${actualTemplateFoo}" = "bar" ] + [ "${actualTemplateBaz}" = "qux" ] +} + +#-------------------------------------------------------------------- +# annotations + +@test "syncCatalogCleanupJob/Uninstall: no annotations defined by default" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations | + del(."consul.hashicorp.com/connect-inject") | + del(."consul.hashicorp.com/mesh-inject")' | + tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "syncCatalogCleanupJob/Uninstall: annotations can be set" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.annotations=foo: bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} + +@test "syncCatalogCleanupJob/Uninstall: metrics annotations can be set" { + cd $(chart_dir) + local object=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.metrics.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations | + del(."consul.hashicorp.com/connect-inject") | + del(."consul.hashicorp.com/mesh-inject")' | + tee /dev/stderr) + + # Annotations to check + annotations=("prometheus.io/scrape" "prometheus.io/path" "prometheus.io/port") + + # Check each annotation + for annotation in "${annotations[@]}"; do + actual=$(echo "$object" | yq -r "has(\"$annotation\")") + [ "$actual" = "true" ] + done +} + +#-------------------------------------------------------------------- +# logLevel + +@test "syncCatalogCleanupJob/Uninstall: logLevel info by default from global" { + cd $(chart_dir) + local cmd=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=info"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "syncCatalogCleanupJob/Uninstall: logLevel can be overridden" { + cd $(chart_dir) + local cmd=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.logLevel=debug' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=debug"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# Vault + +@test "syncCatalogCleanupJob/Uninstall: configures server CA to come from vault when vault is enabled" { + cd $(chart_dir) + local object=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + . | tee /dev/stderr | + yq -r '.spec.template' | tee /dev/stderr) + + # Check annotations + local actual + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-init-first"]' | tee /dev/stderr) + [ "${actual}" = "true" ] + local actual + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject"]' | tee /dev/stderr) + [ "${actual}" = "true" ] + local actual + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr) + [ "${actual}" = "carole" ] + local actual + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-secret-serverca.crt"]' | tee /dev/stderr) + [ "${actual}" = "foo" ] + local actual + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-template-serverca.crt"]' | tee /dev/stderr) + [ "${actual}" = $'{{- with secret \"foo\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' ] + + actual=$(echo $object | jq -r '.spec.volumes[] | select( .name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" = "" ] + + actual=$(echo $object | jq -r '.spec.containers[0].volumeMounts[] | select( .name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" = "" ] +} + +@test "syncCatalogCleanupJob/Uninstall: vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set" { + cd $(chart_dir) + local cmd=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'global.secretsBackend.vault.vaultNamespace=vns' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.tls.enableAutoEncrypt=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata' | tee /dev/stderr) + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/namespace"]' | tee /dev/stderr)" + [ "${actual}" = "vns" ] +} + +@test "syncCatalogCleanupJob/Uninstall: correct vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set and agentAnnotations are also set without vaultNamespace annotation" { + cd $(chart_dir) + local cmd=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'global.secretsBackend.vault.vaultNamespace=vns' \ + --set 'global.secretsBackend.vault.agentAnnotations=vault.hashicorp.com/agent-extra-secret: bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.tls.enableAutoEncrypt=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata' | tee /dev/stderr) + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/namespace"]' | tee /dev/stderr)" + [ "${actual}" = "vns" ] +} + +@test "syncCatalogCleanupJob/Uninstall: correct vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set and agentAnnotations are also set with vaultNamespace annotation" { + cd $(chart_dir) + local cmd=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'global.secretsBackend.vault.vaultNamespace=vns' \ + --set 'global.secretsBackend.vault.agentAnnotations=vault.hashicorp.com/namespace: bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.tls.enableAutoEncrypt=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata' | tee /dev/stderr) + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/namespace"]' | tee /dev/stderr)" + [ "${actual}" = "bar" ] +} + +@test "syncCatalogCleanupJob/Uninstall: vault CA is not configured by default" { + cd $(chart_dir) + local object=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=test' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + . | tee /dev/stderr | + yq -r '.spec.template' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/agent-extra-secret")') + [ "${actual}" = "false" ] + local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/ca-cert")') + [ "${actual}" = "false" ] +} + +@test "syncCatalogCleanupJob/Uninstall: vault CA is not configured when secretName is set but secretKey is not" { + cd $(chart_dir) + local object=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=test' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.ca.secretName=ca' \ + . | tee /dev/stderr | + yq -r '.spec.template' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/agent-extra-secret")') + [ "${actual}" = "false" ] + local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/ca-cert")') + [ "${actual}" = "false" ] +} + +@test "syncCatalogCleanupJob/Uninstall: vault CA is not configured when secretKey is set but secretName is not" { + cd $(chart_dir) + local object=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=test' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.ca.secretKey=tls.crt' \ + . | tee /dev/stderr | + yq -r '.spec.template' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/agent-extra-secret")') + [ "${actual}" = "false" ] + local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/ca-cert")') + [ "${actual}" = "false" ] +} + +@test "syncCatalogCleanupJob/Uninstall: vault CA is configured when both secretName and secretKey are set" { + cd $(chart_dir) + local object=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=test' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.ca.secretName=ca' \ + --set 'global.secretsBackend.vault.ca.secretKey=tls.crt' \ + . | tee /dev/stderr | + yq -r '.spec.template' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.metadata.annotations."vault.hashicorp.com/agent-extra-secret"') + [ "${actual}" = "ca" ] + local actual=$(echo $object | yq -r '.metadata.annotations."vault.hashicorp.com/ca-cert"') + [ "${actual}" = "/vault/custom/tls.crt" ] +} + +#-------------------------------------------------------------------- +# Vault agent annotations + +@test "syncCatalogCleanupJob/Uninstall: no vault agent annotations defined by default" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations | + del(."consul.hashicorp.com/connect-inject") | + del(."consul.hashicorp.com/mesh-inject") | + del(."vault.hashicorp.com/agent-inject") | + del(."vault.hashicorp.com/role")' | + tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "syncCatalogCleanupJob/Uninstall: vault agent annotations can be set" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} diff --git a/charts/consul/test/unit/sync-catalog-cleanup-on-upgrade-job.bats b/charts/consul/test/unit/sync-catalog-cleanup-on-upgrade-job.bats new file mode 100644 index 0000000000..01c7a4f45e --- /dev/null +++ b/charts/consul/test/unit/sync-catalog-cleanup-on-upgrade-job.bats @@ -0,0 +1,896 @@ +#!/usr/bin/env bats + +load _helpers + +target=templates/sync-catalog-cleanup-on-upgrade-job.yaml + +@test "syncCatalogCleanupJob/Upgrade: disabled by default" { + cd $(chart_dir) + assert_empty helm template \ + -s $target \ + . +} + +@test "syncCatalogCleanupJob/Upgrade: disabled with syncCatalog.cleanupNodeOnRemoval true and syncCatalog.enabled true and Release.IsUpgrade true" { + cd $(chart_dir) + assert_empty helm template \ + -s $target \ + --set 'syncCatalog.enabled=true' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --is-upgrade \ + . +} + +@test "syncCatalogCleanupJob/Upgrade: enable with syncCatalog.cleanupNodeOnRemoval true and syncCatalog.enabled false and Release.IsUpgrade true" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --is-upgrade \ + . | tee /dev/stderr | + yq -s 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# image + +@test "syncCatalogCleanupJob/Upgrade: image defaults to global.imageK8S" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'global.imageK8S=bar' \ + --set 'syncCatalog.enabled=false' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --is-upgrade \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].image' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} + +@test "syncCatalogCleanupJob/Upgrade: image can be overridden with server.image" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'global.imageK8S=foo' \ + --set 'syncCatalog.enabled=false' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.image=bar' \ + --is-upgrade \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].image' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} + +@test "syncCatalogCleanupJob/Upgrade: consul env defaults" { + cd $(chart_dir) + local env=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --is-upgrade \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[]' | tee /dev/stderr) + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_ADDRESSES").value' | tee /dev/stderr) + [ "${actual}" = "release-name-consul-server.default.svc" ] + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_GRPC_PORT").value' | tee /dev/stderr) + [ "${actual}" = "8502" ] + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_HTTP_PORT").value' | tee /dev/stderr) + [ "${actual}" = "8500" ] + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_DATACENTER").value' | tee /dev/stderr) + [ "${actual}" = "dc1" ] + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_API_TIMEOUT").value' | tee /dev/stderr) + [ "${actual}" = "5s" ] +} + +#-------------------------------------------------------------------- +# consulNodeName + +@test "syncCatalogCleanupJob/Upgrade: consulNodeName defaults to k8s-sync" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --is-upgrade \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-consul-node-name=k8s-sync"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "syncCatalogCleanupJob/Upgrade: consulNodeName set to empty" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --is-upgrade \ + --set 'syncCatalog.consulNodeName=' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-consul-node-name"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "syncCatalogCleanupJob/Upgrade: can specify consulNodeName" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --is-upgrade \ + --set 'syncCatalog.consulNodeName=aNodeName' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-consul-node-name=aNodeName"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# serviceAccount + +@test "syncCatalogCleanupJob/Upgrade: serviceAccount set when sync enabled" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --is-upgrade \ + . | tee /dev/stderr | + yq '.spec.template.spec.serviceAccountName | contains("sync-catalog-cleanup")' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# aclSyncToken + +@test "syncCatalogCleanupJob/Upgrade: aclSyncToken disabled when secretName is missing" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --is-upgrade \ + --set 'syncCatalog.aclSyncToken.secretKey=bar' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].env[].name] | any(contains("CONSUL_ACL_TOKEN"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "syncCatalogCleanupJob/Upgrade: aclSyncToken disabled when secretKey is missing" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --is-upgrade \ + --set 'syncCatalog.aclSyncToken.secretName=foo' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].env[].name] | any(contains("CONSUL_ACL_TOKEN"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "syncCatalogCleanupJob/Upgrade: aclSyncToken enabled when secretName and secretKey is provided" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --is-upgrade \ + --set 'syncCatalog.aclSyncToken.secretName=foo' \ + --set 'syncCatalog.aclSyncToken.secretKey=bar' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].env[].name] | any(contains("CONSUL_ACL_TOKEN"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# affinity + +@test "syncCatalogCleanupJob/Upgrade: affinity not set by default" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --is-upgrade \ + . | tee /dev/stderr | + yq '.spec.template.spec.affinity == null' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "syncCatalogCleanupJob/Upgrade: affinity can be set" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.affinity=foobar' \ + . | tee /dev/stderr | + yq '.spec.template.spec | .affinity == "foobar"' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# nodeSelector + +@test "syncCatalogCleanupJob/Upgrade: nodeSelector is not set by default" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.nodeSelector' | tee /dev/stderr) + [ "${actual}" = "null" ] +} + +@test "syncCatalogCleanupJob/Upgrade: nodeSelector is not set by default with sync enabled" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.nodeSelector' | tee /dev/stderr) + [ "${actual}" = "null" ] +} + +@test "syncCatalogCleanupJob/Upgrade: specified nodeSelector" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.nodeSelector=testing' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.nodeSelector' | tee /dev/stderr) + [ "${actual}" = "testing" ] +} + +#-------------------------------------------------------------------- +# tolerations + +@test "syncCatalogCleanupJob/Upgrade: tolerations not set by default" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.tolerations == null' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "syncCatalogCleanupJob/Upgrade: tolerations can be set" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.tolerations=foobar' \ + . | tee /dev/stderr | + yq '.spec.template.spec | .tolerations == "foobar"' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# global.acls.manageSystemACLs + +@test "syncCatalogCleanupJob/Upgrade: ACL auth method env vars are set when acls are enabled" { + cd $(chart_dir) + local env=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[]' | tee /dev/stderr) + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_LOGIN_AUTH_METHOD").value' | tee /dev/stderr) + [ "${actual}" = "release-name-consul-k8s-component-auth-method" ] + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_LOGIN_DATACENTER").value' | tee /dev/stderr) + [ "${actual}" = "dc1" ] + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_LOGIN_META").value' | tee /dev/stderr) + [ "${actual}" = 'component=sync-catalog,pod=$(NAMESPACE)/$(POD_NAME)' ] +} + +@test "syncCatalogCleanupJob/Upgrade: sets global auth method and primary datacenter when federation and acls and namespaces are enabled" { + cd $(chart_dir) + local env=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.federation.enabled=true' \ + --set 'global.federation.primaryDatacenter=dc1' \ + --set 'global.datacenter=dc2' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'global.tls.enabled=true' \ + --set 'meshGateway.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[]' | tee /dev/stderr) + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_LOGIN_AUTH_METHOD").value' | tee /dev/stderr) + [ "${actual}" = "release-name-consul-k8s-component-auth-method-dc2" ] + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_LOGIN_DATACENTER").value' | tee /dev/stderr) + [ "${actual}" = "dc1" ] +} + +@test "syncCatalogCleanupJob/Upgrade: sets default login partition and acls and partitions are enabled" { + cd $(chart_dir) + local env=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enableConsulNamespaces=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[]' | tee /dev/stderr) + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_LOGIN_PARTITION").value' | tee /dev/stderr) + [ "${actual}" = "default" ] +} + +@test "syncCatalogCleanupJob/Upgrade: sets non-default login partition and acls and partitions are enabled" { + cd $(chart_dir) + local env=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.adminPartitions.name=foo' \ + --set 'global.enableConsulNamespaces=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[]' | tee /dev/stderr) + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_LOGIN_PARTITION").value' | tee /dev/stderr) + [ "${actual}" = "foo" ] +} + +#-------------------------------------------------------------------- +# global.tls.enabled + +@test "syncCatalogCleanupJob/Upgrade: sets Consul environment variables when global.tls.enabled" { + cd $(chart_dir) + local env=$(helm template \ + -s $target \ + --set 'client.enabled=true' \ + --is-upgrade \ + --set 'syncCatalog.enabled=false' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].env[]' | tee /dev/stderr) + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_HTTP_PORT").value' | tee /dev/stderr) + [ "${actual}" = "8501" ] + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_USE_TLS").value' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo "$env" | + jq -r '. | select( .name == "CONSUL_CACERT_FILE").value' | tee /dev/stderr) + [ "${actual}" = "/consul/tls/ca/tls.crt" ] +} + +@test "syncCatalogCleanupJob/Upgrade: can overwrite CA secret with the provided one" { + cd $(chart_dir) + local ca_cert_volume=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo-ca-cert' \ + --set 'global.tls.caCert.secretKey=key' \ + --set 'global.tls.caKey.secretName=foo-ca-key' \ + --set 'global.tls.caKey.secretKey=key' \ + . | tee /dev/stderr | + yq '.spec.template.spec.volumes[] | select(.name=="consul-ca-cert")' | tee /dev/stderr) + + # check that the provided ca cert secret is attached as a volume + local actual + actual=$(echo $ca_cert_volume | jq -r '.secret.secretName' | tee /dev/stderr) + [ "${actual}" = "foo-ca-cert" ] + + # check that the volume uses the provided secret key + actual=$(echo $ca_cert_volume | jq -r '.secret.items[0].key' | tee /dev/stderr) + [ "${actual}" = "key" ] +} + +@test "syncCatalogCleanupJob/Upgrade: consul-ca-cert volumeMount is added when TLS is enabled" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "consul-ca-cert") | length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "syncCatalogCleanupJob/Upgrade: consul-ca-cert volume is not added if externalServers.enabled=true and externalServers.useSystemRoots=true" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=foo.com' \ + --set 'externalServers.useSystemRoots=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.volumes[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" = "" ] +} + +#-------------------------------------------------------------------- +# resources + +@test "syncCatalogCleanupJob/Upgrade: default resources" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq -rc '.spec.template.spec.containers[0].resources' | tee /dev/stderr) + [ "${actual}" = '{"limits":{"cpu":"50m","memory":"50Mi"},"requests":{"cpu":"50m","memory":"50Mi"}}' ] +} + +@test "syncCatalogCleanupJob/Upgrade: can set resources" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.resources.requests.memory=100Mi' \ + --set 'syncCatalog.resources.requests.cpu=100m' \ + --set 'syncCatalog.resources.limits.memory=200Mi' \ + --set 'syncCatalog.resources.limits.cpu=200m' \ + . | tee /dev/stderr | + yq -rc '.spec.template.spec.containers[0].resources' | tee /dev/stderr) + [ "${actual}" = '{"limits":{"cpu":"200m","memory":"200Mi"},"requests":{"cpu":"100m","memory":"100Mi"}}' ] +} + +#-------------------------------------------------------------------- +# extraLabels + +@test "syncCatalogCleanupJob/Upgrade: no extra labels defined by default" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component")' | tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "syncCatalogCleanupJob/Upgrade: can set extra labels" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.extraLabels.foo=bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.labels.foo' | tee /dev/stderr) + + [ "${actual}" = "bar" ] +} + +@test "syncCatalogCleanupJob/Upgrade: extra global labels can be set" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.extraLabels.foo=bar' \ + . | tee /dev/stderr) + local actualBar=$(echo "${actual}" | yq -r '.metadata.labels.foo' | tee /dev/stderr) + [ "${actualBar}" = "bar" ] + local actualTemplateBar=$(echo "${actual}" | yq -r '.spec.template.metadata.labels.foo' | tee /dev/stderr) + [ "${actualTemplateBar}" = "bar" ] +} + +@test "syncCatalogCleanupJob/Upgrade: multiple extra global labels can be set" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.extraLabels.foo=bar' \ + --set 'global.extraLabels.baz=qux' \ + . | tee /dev/stderr) + local actualFoo=$(echo "${actual}" | yq -r '.metadata.labels.foo' | tee /dev/stderr) + local actualBaz=$(echo "${actual}" | yq -r '.metadata.labels.baz' | tee /dev/stderr) + [ "${actualFoo}" = "bar" ] + [ "${actualBaz}" = "qux" ] + local actualTemplateFoo=$(echo "${actual}" | yq -r '.spec.template.metadata.labels.foo' | tee /dev/stderr) + local actualTemplateBaz=$(echo "${actual}" | yq -r '.spec.template.metadata.labels.baz' | tee /dev/stderr) + [ "${actualTemplateFoo}" = "bar" ] + [ "${actualTemplateBaz}" = "qux" ] +} + +#-------------------------------------------------------------------- +# annotations + +@test "syncCatalogCleanupJob/Upgrade: no annotations defined by default" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations | + del(."consul.hashicorp.com/connect-inject") | + del(."consul.hashicorp.com/mesh-inject")' | + tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "syncCatalogCleanupJob/Upgrade: annotations can be set" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'syncCatalog.annotations=foo: bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} + +@test "syncCatalogCleanupJob/Upgrade: metrics annotations can be set" { + cd $(chart_dir) + local object=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --is-upgrade \ + --set 'syncCatalog.metrics.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations | + del(."consul.hashicorp.com/connect-inject") | + del(."consul.hashicorp.com/mesh-inject")' | + tee /dev/stderr) + + # Annotations to check + annotations=("prometheus.io/scrape" "prometheus.io/path" "prometheus.io/port") + + # Check each annotation + for annotation in "${annotations[@]}"; do + actual=$(echo "$object" | yq -r "has(\"$annotation\")") + [ "$actual" = "true" ] + done +} + +#-------------------------------------------------------------------- +# logLevel + +@test "syncCatalogCleanupJob/Upgrade: logLevel info by default from global" { + cd $(chart_dir) + local cmd=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=info"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "syncCatalogCleanupJob/Upgrade: logLevel can be overridden" { + cd $(chart_dir) + local cmd=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.logLevel=debug' \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual=$(echo "$cmd" | + yq 'any(contains("-log-level=debug"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# Vault + +@test "syncCatalogCleanupJob/Upgrade: configures server CA to come from vault when vault is enabled" { + cd $(chart_dir) + local object=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + . | tee /dev/stderr | + yq -r '.spec.template' | tee /dev/stderr) + + # Check annotations + local actual + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-init-first"]' | tee /dev/stderr) + [ "${actual}" = "true" ] + local actual + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject"]' | tee /dev/stderr) + [ "${actual}" = "true" ] + local actual + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr) + [ "${actual}" = "carole" ] + local actual + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-secret-serverca.crt"]' | tee /dev/stderr) + [ "${actual}" = "foo" ] + local actual + actual=$(echo $object | jq -r '.metadata.annotations["vault.hashicorp.com/agent-inject-template-serverca.crt"]' | tee /dev/stderr) + [ "${actual}" = $'{{- with secret \"foo\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' ] + + actual=$(echo $object | jq -r '.spec.volumes[] | select( .name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" = "" ] + + actual=$(echo $object | jq -r '.spec.containers[0].volumeMounts[] | select( .name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" = "" ] +} + +@test "syncCatalogCleanupJob/Upgrade: vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set" { + cd $(chart_dir) + local cmd=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'global.secretsBackend.vault.vaultNamespace=vns' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.tls.enableAutoEncrypt=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata' | tee /dev/stderr) + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/namespace"]' | tee /dev/stderr)" + [ "${actual}" = "vns" ] +} + +@test "syncCatalogCleanupJob/Upgrade: correct vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set and agentAnnotations are also set without vaultNamespace annotation" { + cd $(chart_dir) + local cmd=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'global.secretsBackend.vault.vaultNamespace=vns' \ + --set 'global.secretsBackend.vault.agentAnnotations=vault.hashicorp.com/agent-extra-secret: bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.tls.enableAutoEncrypt=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata' | tee /dev/stderr) + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/namespace"]' | tee /dev/stderr)" + [ "${actual}" = "vns" ] +} + +@test "syncCatalogCleanupJob/Upgrade: correct vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set and agentAnnotations are also set with vaultNamespace annotation" { + cd $(chart_dir) + local cmd=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'global.secretsBackend.vault.vaultNamespace=vns' \ + --set 'global.secretsBackend.vault.agentAnnotations=vault.hashicorp.com/namespace: bar' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.tls.enableAutoEncrypt=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata' | tee /dev/stderr) + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/namespace"]' | tee /dev/stderr)" + [ "${actual}" = "bar" ] +} + +@test "syncCatalogCleanupJob/Upgrade: vault CA is not configured by default" { + cd $(chart_dir) + local object=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=test' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + . | tee /dev/stderr | + yq -r '.spec.template' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/agent-extra-secret")') + [ "${actual}" = "false" ] + local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/ca-cert")') + [ "${actual}" = "false" ] +} + +@test "syncCatalogCleanupJob/Upgrade: vault CA is not configured when secretName is set but secretKey is not" { + cd $(chart_dir) + local object=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=test' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.ca.secretName=ca' \ + . | tee /dev/stderr | + yq -r '.spec.template' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/agent-extra-secret")') + [ "${actual}" = "false" ] + local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/ca-cert")') + [ "${actual}" = "false" ] +} + +@test "syncCatalogCleanupJob/Upgrade: vault CA is not configured when secretKey is set but secretName is not" { + cd $(chart_dir) + local object=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=test' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.ca.secretKey=tls.crt' \ + . | tee /dev/stderr | + yq -r '.spec.template' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/agent-extra-secret")') + [ "${actual}" = "false" ] + local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/ca-cert")') + [ "${actual}" = "false" ] +} + +@test "syncCatalogCleanupJob/Upgrade: vault CA is configured when both secretName and secretKey are set" { + cd $(chart_dir) + local object=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=test' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.ca.secretName=ca' \ + --set 'global.secretsBackend.vault.ca.secretKey=tls.crt' \ + . | tee /dev/stderr | + yq -r '.spec.template' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.metadata.annotations."vault.hashicorp.com/agent-extra-secret"') + [ "${actual}" = "ca" ] + local actual=$(echo $object | yq -r '.metadata.annotations."vault.hashicorp.com/ca-cert"') + [ "${actual}" = "/vault/custom/tls.crt" ] +} + +#-------------------------------------------------------------------- +# Vault agent annotations + +@test "syncCatalogCleanupJob/Upgrade: no vault agent annotations defined by default" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations | + del(."consul.hashicorp.com/connect-inject") | + del(."consul.hashicorp.com/mesh-inject") | + del(."vault.hashicorp.com/agent-inject") | + del(."vault.hashicorp.com/role")' | + tee /dev/stderr) + [ "${actual}" = "{}" ] +} + +@test "syncCatalogCleanupJob/Upgrade: vault agent annotations can be set" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.enabled=false' \ + --is-upgrade \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} diff --git a/charts/consul/test/unit/sync-catalog-cleanup-serviceaccount.bats b/charts/consul/test/unit/sync-catalog-cleanup-serviceaccount.bats new file mode 100644 index 0000000000..8362eb87c8 --- /dev/null +++ b/charts/consul/test/unit/sync-catalog-cleanup-serviceaccount.bats @@ -0,0 +1,75 @@ +#!/usr/bin/env bats + +load _helpers + +target=templates/sync-catalog-cleanup-serviceaccount.yaml + +@test "syncCatalogCleanup/ServiceAccount: disabled by default" { + cd $(chart_dir) + assert_empty helm template \ + -s $target \ + . +} + +@test "syncCatalogCleanup/ServiceAccount: disabled with cleanup disabled" { + cd $(chart_dir) + assert_empty helm template \ + -s $target \ + --set 'syncCatalog.cleanupNodeOnRemoval=false' \ + . +} + +@test "syncCatalogCleanup/ServiceAccount: enabled with cleanup enabled" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# global.imagePullSecrets + +@test "syncCatalogCleanup/ServiceAccount: can set image pull secrets" { + cd $(chart_dir) + local object=$(helm template \ + -s $target \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set 'global.imagePullSecrets[0].name=my-secret' \ + --set 'global.imagePullSecrets[1].name=my-secret2' \ + . | tee /dev/stderr) + + local actual=$(echo "$object" | + yq -r '.imagePullSecrets[0].name' | tee /dev/stderr) + [ "${actual}" = "my-secret" ] + + local actual=$(echo "$object" | + yq -r '.imagePullSecrets[1].name' | tee /dev/stderr) + [ "${actual}" = "my-secret2" ] +} + +#-------------------------------------------------------------------- +# syncCatalog.serviceAccount.annotations + +@test "syncCatalogCleanup/ServiceAccount: no annotations by default" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + . | tee /dev/stderr | + yq '.metadata.annotations | length > 0' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "syncCatalogCleanup/ServiceAccount: annotations when enabled" { + cd $(chart_dir) + local actual=$(helm template \ + -s $target \ + --set 'syncCatalog.cleanupNodeOnRemoval=true' \ + --set "syncCatalog.serviceAccount.annotations=foo: bar" \ + . | tee /dev/stderr | + yq -r '.metadata.annotations.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index fa2af53ee4..8ede5520fb 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -66,7 +66,7 @@ global: # image: "hashicorp/consul-enterprise:1.10.0-ent" # ``` # @default: hashicorp/consul: - image: docker.mirror.hashicorp.services/hashicorppreview/consul:1.20-dev + image: hashicorp/consul:1.20.2 # Array of objects containing image pull secret names that will be applied to each service account. # This can be used to reference image pull secrets if using a custom consul or consul-k8s-control-plane Docker image. @@ -86,7 +86,7 @@ global: # image that is used for functionality such as catalog sync. # This can be overridden per component. # @default: hashicorp/consul-k8s-control-plane: - imageK8S: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.6-dev + imageK8S: hashicorp/consul-k8s-control-plane:1.6.2 # The image pull policy used globally for images controlled by Consul (consul, consul-dataplane, consul-k8s, consul-telemetry-collector). # One of "IfNotPresent", "Always", "Never", and "". Refer to https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy @@ -793,7 +793,7 @@ global: # The name (and tag) of the consul-dataplane Docker image used for the # connect-injected sidecar proxies and mesh, terminating, and ingress gateways. # @default: hashicorp/consul-dataplane: - imageConsulDataplane: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.6-dev + imageConsulDataplane: hashicorp/consul-dataplane:1.6.2 # Configuration for running this Helm chart on the Red Hat OpenShift platform. # This Helm chart currently supports OpenShift v4.x+. @@ -2090,6 +2090,10 @@ syncCatalog: # global.enabled. enabled: false + # True if you want to deregister all services in this cluster from consul that have been registered by the catalog sync when the `enabled` flag is set to false. + # @type: boolean + cleanupNodeOnRemoval: false + # The name of the Docker image (including any tag) for consul-k8s-control-plane # to run the sync program. # @type: string @@ -2771,6 +2775,7 @@ connectInject: # By default, we exclude kube-system since usually users won't # want those pods injected and local-path-storage and openebs so that # Kind (Kubernetes In Docker) and [OpenEBS](https://openebs.io/) respectively can provision Pods used to create PVCs. + # We also exclude gmp-system and gke-managed-cim namespaces that are used by GKE for managing the cluster. # Note that this exclusion is only supported in Kubernetes v1.21.1+. # # Example: @@ -2785,7 +2790,7 @@ connectInject: matchExpressions: - key: "kubernetes.io/metadata.name" operator: "NotIn" - values: ["kube-system","local-path-storage","openebs"] + values: ["kube-system","local-path-storage","openebs","gmp-system","gke-managed-cim"] # List of k8s namespaces to allow Connect sidecar # injection in. If a k8s namespace is not included or is listed in `k8sDenyNamespaces`, diff --git a/cli/cmd/gateway/list/command.go b/cli/cmd/gateway/list/command.go new file mode 100644 index 0000000000..ef6fc5088b --- /dev/null +++ b/cli/cmd/gateway/list/command.go @@ -0,0 +1,312 @@ +package read + +import ( + "archive/zip" + "context" + "encoding/json" + "fmt" + "os" + "strings" + "sync" + + helmcli "helm.sh/helm/v3/pkg/cli" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + "sigs.k8s.io/yaml" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/flag" + "github.com/hashicorp/consul-k8s/cli/common/terminal" +) + +type Command struct { + *common.BaseCommand + + kubernetes client.Client + restConfig *rest.Config + + set *flag.Sets + + flagAllNamespaces bool + flagGatewayKind string + flagGatewayNamespace string + flagKubeConfig string + flagKubeContext string + flagOutput string + + initOnce sync.Once + help string +} + +func (c *Command) Help() string { + c.initOnce.Do(c.init) + return fmt.Sprintf("%s\n\nUsage: consul-k8s gateway list [flags]\n\n%s", c.Synopsis(), c.help) +} + +func (c *Command) Synopsis() string { + return "Inspect the configuration for all Gateways in the Kubernetes cluster or a given Kubernetes namespace." +} + +// init establishes the flags for Command +func (c *Command) init() { + c.set = flag.NewSets() + + f := c.set.NewSet("Command Options") + f.StringVar(&flag.StringVar{ + Name: "namespace", + Target: &c.flagGatewayNamespace, + Usage: "The Kubernetes namespace to list Gateways in.", + Aliases: []string{"n"}, + }) + f.BoolVar(&flag.BoolVar{ + Name: "all-namespaces", + Target: &c.flagAllNamespaces, + Default: false, + Usage: "List Gateways in all Kubernetes namespaces.", + Aliases: []string{"A"}, + }) + f.StringVar(&flag.StringVar{ + Name: "output", + Target: &c.flagOutput, + Usage: "Output the Gateway configuration as 'json' in the terminal or 'archive' as a zip archive named 'gateways.zip' in the current directory.", + Default: "archive", + Aliases: []string{"o"}, + }) + + f = c.set.NewSet("Global Options") + f.StringVar(&flag.StringVar{ + Name: "kubeconfig", + Aliases: []string{"c"}, + Target: &c.flagKubeConfig, + Usage: "Set the path to a kubeconfig file.", + }) + f.StringVar(&flag.StringVar{ + Name: "context", + Target: &c.flagKubeContext, + Usage: "Set the Kubernetes context to use.", + }) + + c.help = c.set.Help() +} + +// Run runs the command +func (c *Command) Run(args []string) int { + c.initOnce.Do(c.init) + c.Log.ResetNamed("read") + defer common.CloseWithError(c.BaseCommand) + + if err := c.set.Parse(args); err != nil { + c.UI.Output(err.Error(), terminal.WithErrorStyle()) + return 1 + } + + if err := c.initKubernetes(); err != nil { + c.UI.Output(err.Error(), terminal.WithErrorStyle()) + return 1 + } + + if err := c.fetchCRDs(); err != nil { + c.UI.Output(err.Error(), terminal.WithErrorStyle()) + return 1 + } + + return 0 +} + +type gatewayWithRoutes struct { + Gateway gwv1beta1.Gateway `json:"gateway"` + GatewayClass gwv1beta1.GatewayClass `json:"gatewayClass"` + HTTPRoutes []gwv1beta1.HTTPRoute `json:"httpRoutes"` + TCPRoutes []gwv1alpha2.TCPRoute `json:"tcpRoutes"` +} + +func (c *Command) fetchCRDs() error { + // Fetch Gateways + var gateways gwv1beta1.GatewayList + if err := c.kubernetes.List(context.Background(), &gateways, client.InNamespace(c.flagGatewayNamespace)); err != nil { + return fmt.Errorf("error fetching Gateway CRD: %w", err) + } + + // Fetch all HTTPRoutes in all namespaces + var httpRoutes gwv1beta1.HTTPRouteList + if err := c.kubernetes.List(context.Background(), &httpRoutes); err != nil { + return fmt.Errorf("error fetching HTTPRoute CRDs: %w", err) + } + + // Fetch all TCPRoutes in all namespaces + var tcpRoutes gwv1alpha2.TCPRouteList + if err := c.kubernetes.List(context.Background(), &tcpRoutes); err != nil { + return fmt.Errorf("error fetching TCPRoute CRDs: %w", err) + } + + var gws []gatewayWithRoutes + + for _, gateway := range gateways.Items { + // Fetch GatewayClass referenced by Gateway + var gatewayClass gwv1beta1.GatewayClass + if err := c.kubernetes.Get(context.Background(), client.ObjectKey{Namespace: "", Name: string(gateway.Spec.GatewayClassName)}, &gatewayClass); err != nil { + return fmt.Errorf("error fetching GatewayClass CRD: %w", err) + } + + // FUTURE Fetch GatewayClassConfig referenced by GatewayClass + // This import requires resolving hairy dependency discrepancies between modules in this repo + //var gatewayClassConfig v1alpha1.GatewayClassConfig + //if err := c.kubernetes.Get(context.Background(), client.ObjectKey{Namespace: "", Name: gatewayClass.Spec.ParametersRef.Name}, &gatewayClassConfig); err != nil { + // return fmt.Errorf("error fetching GatewayClassConfig CRD: %w", err) + //} + + // FUTURE Fetch MeshServices referenced by HTTPRoutes or TCPRoutes + // This import requires resolving hairy dependency discrepancies between modules in this repo + // var meshServices v1alpha1.MeshServiceList + // if err := c.kubernetes.List(context.Background(), &meshServices); err != nil { + // return fmt.Errorf("error fetching MeshService CRDs: %w", err) + // } + + gw := gatewayWithRoutes{ + Gateway: gateway, + GatewayClass: gatewayClass, + HTTPRoutes: make([]gwv1beta1.HTTPRoute, 0, len(httpRoutes.Items)), + TCPRoutes: make([]gwv1alpha2.TCPRoute, 0, len(tcpRoutes.Items)), + } + + for _, route := range httpRoutes.Items { + for _, ref := range route.Spec.ParentRefs { + switch { + case string(ref.Name) != gateway.Name: + // Route parent references gateway with different name + continue + case ref.Namespace != nil && string(*ref.Namespace) == gateway.Namespace: + // Route parent explicitly references gateway with same name and namespace + gw.HTTPRoutes = append(gw.HTTPRoutes, route) + case ref.Namespace == nil && route.Namespace == gateway.Namespace: + // Route parent implicitly references gateway with same name in local namespace + gw.HTTPRoutes = append(gw.HTTPRoutes, route) + } + } + } + + for _, route := range tcpRoutes.Items { + for _, ref := range route.Spec.ParentRefs { + switch { + case string(ref.Name) != gateway.Name: + // Route parent references gateway with different name + continue + case ref.Namespace != nil && string(*ref.Namespace) == gateway.Namespace: + // Route parent explicitly references gateway with same name and namespace + gw.TCPRoutes = append(gw.TCPRoutes, route) + case ref.Namespace == nil && route.Namespace == gateway.Namespace: + // Route parent implicitly references gateway with same name in local namespace + gw.TCPRoutes = append(gw.TCPRoutes, route) + } + } + } + + gws = append(gws, gw) + } + + switch strings.ToLower(c.flagOutput) { + case "json": + if err := c.writeJSONOutput(gws); err != nil { + return fmt.Errorf("error writing CRDs as JSON: %w", err) + } + default: + file, err := os.Create("./gateways.zip") + if err != nil { + return fmt.Errorf("error creating output file: %w", err) + } + + zipw := zip.NewWriter(file) + defer zipw.Close() + + if err := c.writeArchive(zipw, gws); err != nil { + return fmt.Errorf("error writing CRDs to zip archive: %w", err) + } + c.UI.Output("Wrote to zip archive " + file.Name()) + return zipw.Close() + } + + return nil +} + +func (c *Command) writeJSONOutput(obj interface{}) error { + output, err := json.MarshalIndent(obj, "", "\t") + if err != nil { + return err + } + + c.UI.Output(string(output)) + return nil +} + +// writeArchive writes one file to the zip archive for each Gateway in the list. +// The files have name `-.yaml`. +func (c *Command) writeArchive(zipw *zip.Writer, gws []gatewayWithRoutes) error { + for _, gw := range gws { + name := fmt.Sprintf("%s-%s.yaml", gw.Gateway.Namespace, gw.Gateway.Name) + + w, err := zipw.Create(name) + if err != nil { + return fmt.Errorf("error creating zip entry for %s: %w", name, err) + } + + objYaml, err := yaml.Marshal(gw) + if err != nil { + return fmt.Errorf("error marshalling %s: %w", name, err) + } + + _, err = w.Write(objYaml) + if err != nil { + return fmt.Errorf("error writing %s to zip archive: %w", name, err) + } + } + + return nil +} + +// initKubernetes initializes the REST config and uses it to initialize the k8s client. +func (c *Command) initKubernetes() (err error) { + settings := helmcli.New() + + // If a kubeconfig was specified, use it + if c.flagKubeConfig != "" { + settings.KubeConfig = c.flagKubeConfig + } + + // If a kube context was specified, use it + if c.flagKubeContext != "" { + settings.KubeContext = c.flagKubeContext + } + + // Create a REST config from the settings for our Kubernetes client + if c.restConfig == nil { + if c.restConfig, err = settings.RESTClientGetter().ToRESTConfig(); err != nil { + return fmt.Errorf("error creating Kubernetes REST config: %w", err) + } + } + + // Create a controller-runtime client from c.restConfig + if c.kubernetes == nil { + if c.kubernetes, err = client.New(c.restConfig, client.Options{}); err != nil { + return fmt.Errorf("error creating controller-runtime client: %w", err) + } + // FUTURE Fix dependency discrepancies between modules in this repo so that this scheme can be added (see above) + //_ = v1alpha1.AddToScheme(c.kubernetes.Scheme()) + _ = gwv1alpha2.AddToScheme(c.kubernetes.Scheme()) + _ = gwv1beta1.AddToScheme(c.kubernetes.Scheme()) + } + + // If all namespaces specified, use empty namespace; otherwise, if + // no namespace was specified, use the one from the kube context. + if c.flagAllNamespaces { + c.flagGatewayNamespace = "" + } else if c.flagGatewayNamespace == "" { + if c.flagOutput != "json" { + c.UI.Output("No namespace specified, using current kube context namespace: %s", settings.Namespace()) + } + c.flagGatewayNamespace = settings.Namespace() + } + + return nil +} diff --git a/cli/cmd/gateway/list/command_test.go b/cli/cmd/gateway/list/command_test.go new file mode 100644 index 0000000000..e61d28a3ab --- /dev/null +++ b/cli/cmd/gateway/list/command_test.go @@ -0,0 +1,160 @@ +package read + +import ( + "bytes" + "context" + "encoding/json" + "io" + "os" + "testing" + + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/terminal" +) + +func TestFlagParsing(t *testing.T) { + cases := map[string]struct { + args []string + out int + }{ + "No args": { + args: []string{}, + out: 1, + }, + "Multiple gateway names passed": { + args: []string{"gateway-1", "gateway-2"}, + out: 1, + }, + "Nonexistent flag passed, -foo bar": { + args: []string{"gateway-1", "-foo", "bar"}, + out: 1, + }, + "Invalid argument passed, -namespace YOLO": { + args: []string{"gateway-1", "-namespace", "YOLO"}, + out: 1, + }, + "User passed incorrect output": { + args: []string{"gateway-1", "-output", "image"}, + out: 1, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + c := setupCommand(new(bytes.Buffer)) + c.kubernetes = fake.NewClientBuilder().WithObjectTracker(nil).Build() + + out := c.Run(tc.args) + require.Equal(t, tc.out, out) + }) + } +} + +func TestReadCommandOutput(t *testing.T) { + gatewayClassName := "gateway-class-1" + gatewayName := "gateway-1" + routeName := "route-1" + + fakeGatewayClass := &gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: gatewayClassName, + }, + } + + fakeGateway := &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: gatewayName, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gwv1beta1.ObjectName(gatewayClassName), + }, + } + + fakeHTTPRoute := &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: routeName, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Name: gwv1beta1.ObjectName(fakeGateway.Name), + }, + }, + }, + }, + } + + fakeUnattachedHTTPRoute := &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "route-2", + }, + } + + buf := new(bytes.Buffer) + c := setupCommand(buf) + + scheme := scheme.Scheme + gwv1beta1.AddToScheme(scheme) + gwv1alpha2.AddToScheme(scheme) + + c.kubernetes = fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(fakeGatewayClass, fakeGateway, fakeHTTPRoute, fakeUnattachedHTTPRoute). + Build() + + out := c.Run([]string{"-output", "json"}) + require.Equal(t, 0, out) + + gatewaysWithRoutes := []struct { + Gateway gwv1beta1.Gateway `json:"Gateway"` + GatewayClass gwv1beta1.GatewayClass `json:"GatewayClass"` + HTTPRoutes []gwv1beta1.HTTPRoute `json:"HTTPRoutes"` + }{} + require.NoErrorf(t, json.Unmarshal(buf.Bytes(), &gatewaysWithRoutes), "failed to parse JSON output %s", buf.String()) + require.Len(t, gatewaysWithRoutes, 1) + + gatewayWithRoutes := gatewaysWithRoutes[0] + + // Make gateway assertions + assert.Equal(t, gatewayName, gatewayWithRoutes.Gateway.Name) + + // Make gateway class assertions + assert.Equal(t, gatewayClassName, gatewayWithRoutes.GatewayClass.Name) + + // Make http route assertions + require.Len(t, gatewayWithRoutes.HTTPRoutes, 1) + assert.Equal(t, routeName, gatewayWithRoutes.HTTPRoutes[0].Name) +} + +func setupCommand(buf io.Writer) *Command { + // Log at a test level to standard out. + log := hclog.New(&hclog.LoggerOptions{ + Name: "test", + Level: hclog.Debug, + Output: os.Stdout, + }) + + // Setup and initialize the command struct + command := &Command{ + BaseCommand: &common.BaseCommand{ + Log: log, + UI: terminal.NewUI(context.Background(), buf), + }, + } + command.init() + + return command +} diff --git a/cli/cmd/gateway/read/command.go b/cli/cmd/gateway/read/command.go new file mode 100644 index 0000000000..069ea529d7 --- /dev/null +++ b/cli/cmd/gateway/read/command.go @@ -0,0 +1,299 @@ +package read + +import ( + "archive/zip" + "context" + "encoding/json" + "fmt" + "os" + "strings" + "sync" + + helmcli "helm.sh/helm/v3/pkg/cli" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + "sigs.k8s.io/yaml" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/flag" + "github.com/hashicorp/consul-k8s/cli/common/terminal" +) + +type Command struct { + *common.BaseCommand + + kubernetes client.Client + restConfig *rest.Config + + set *flag.Sets + + flagGatewayKind string + flagGatewayNamespace string + flagKubeConfig string + flagKubeContext string + flagOutput string + + gatewayName string + + initOnce sync.Once + help string +} + +func (c *Command) Help() string { + c.initOnce.Do(c.init) + return fmt.Sprintf("%s\n\nUsage: consul-k8s gateway read [flags]\n\n%s", c.Synopsis(), c.help) +} + +func (c *Command) Synopsis() string { + return "Inspect the configuration for a given Gateway." +} + +// init establishes the flags for Command +func (c *Command) init() { + c.set = flag.NewSets() + + f := c.set.NewSet("Command Options") + f.StringVar(&flag.StringVar{ + Name: "namespace", + Target: &c.flagGatewayNamespace, + Usage: "The Kubernetes namespace of the Gateway to read", + Aliases: []string{"n"}, + }) + f.StringVar(&flag.StringVar{ + Name: "output", + Target: &c.flagOutput, + Usage: "Output the Gateway configuration as 'json' in the terminal or 'archive' as a zip archive in the current directory named after the Gateway.", + Default: "archive", + Aliases: []string{"o"}, + }) + + f = c.set.NewSet("Global Options") + f.StringVar(&flag.StringVar{ + Name: "kubeconfig", + Aliases: []string{"c"}, + Target: &c.flagKubeConfig, + Usage: "Set the path to kubeconfig file.", + }) + f.StringVar(&flag.StringVar{ + Name: "context", + Target: &c.flagKubeContext, + Usage: "Set the Kubernetes context to use.", + }) + + c.help = c.set.Help() +} + +// Run runs the command +func (c *Command) Run(args []string) int { + c.initOnce.Do(c.init) + c.Log.ResetNamed("read") + defer common.CloseWithError(c.BaseCommand) + + if len(args) < 1 || strings.HasPrefix(args[0], "-") { + c.UI.Output("Usage: gateway read ") + return 1 + } + + if len(args) > 1 { + if err := c.set.Parse(args[1:]); err != nil { + c.UI.Output(err.Error(), terminal.WithErrorStyle()) + return 1 + } + } + + c.gatewayName = args[0] + + if err := c.initKubernetes(); err != nil { + c.UI.Output(err.Error(), terminal.WithErrorStyle()) + return 1 + } + + if err := c.fetchCRDs(); err != nil { + c.UI.Output(err.Error(), terminal.WithErrorStyle()) + return 1 + } + + return 0 +} + +func (c *Command) fetchCRDs() error { + // Fetch Gateway + var gateway gwv1beta1.Gateway + if err := c.kubernetes.Get(context.Background(), client.ObjectKey{Namespace: c.flagGatewayNamespace, Name: c.gatewayName}, &gateway); err != nil { + return fmt.Errorf("error fetching Gateway CRD: %w", err) + } + + // Fetch GatewayClass referenced by Gateway + var gatewayClass gwv1beta1.GatewayClass + if err := c.kubernetes.Get(context.Background(), client.ObjectKey{Namespace: "", Name: string(gateway.Spec.GatewayClassName)}, &gatewayClass); err != nil { + return fmt.Errorf("error fetching GatewayClass CRD: %w", err) + } + + // FUTURE Fetch GatewayClassConfig referenced by GatewayClass + // This import requires resolving hairy dependency discrepancies between modules in this repo + //var gatewayClassConfig v1alpha1.GatewayClassConfig + //if err := c.kubernetes.Get(context.Background(), client.ObjectKey{Namespace: "", Name: gatewayClass.Spec.ParametersRef.Name}, &gatewayClassConfig); err != nil { + // return fmt.Errorf("error fetching GatewayClassConfig CRD: %w", err) + //} + + // Fetch HTTPRoutes that reference the Gateway + var httpRoutes gwv1beta1.HTTPRouteList + if err := c.kubernetes.List(context.Background(), &httpRoutes); err != nil { + return fmt.Errorf("error fetching HTTPRoute CRDs: %w", err) + } + + // Fetch TCPRoutes that reference the Gateway + var tcpRoutes gwv1alpha2.TCPRouteList + if err := c.kubernetes.List(context.Background(), &tcpRoutes); err != nil { + return fmt.Errorf("error fetching TCPRoute CRDs: %w", err) + } + + // FUTURE Fetch MeshServices referenced by HTTPRoutes or TCPRoutes + // This import requires resolving hairy dependency discrepancies between modules in this repo + // var meshServices v1alpha1.MeshServiceList + // if err := c.kubernetes.List(context.Background(), &meshServices); err != nil { + // return fmt.Errorf("error fetching MeshService CRDs: %w", err) + // } + + gatewayWithRoutes := struct { + Gateway gwv1beta1.Gateway `json:"gateway"` + GatewayClass gwv1beta1.GatewayClass `json:"gatewayClass"` + HTTPRoutes []gwv1beta1.HTTPRoute `json:"httpRoutes"` + TCPRoutes []gwv1alpha2.TCPRoute `json:"tcpRoutes"` + }{ + Gateway: gateway, + GatewayClass: gatewayClass, + HTTPRoutes: make([]gwv1beta1.HTTPRoute, 0, len(httpRoutes.Items)), + TCPRoutes: make([]gwv1alpha2.TCPRoute, 0, len(tcpRoutes.Items)), + } + + for _, route := range httpRoutes.Items { + for _, ref := range route.Spec.ParentRefs { + switch { + case string(ref.Name) != gateway.Name: + // Route parent references gateway with different name + continue + case ref.Namespace != nil && string(*ref.Namespace) == gateway.Namespace: + // Route parent explicitly references gateway with same name and namespace + gatewayWithRoutes.HTTPRoutes = append(gatewayWithRoutes.HTTPRoutes, route) + case ref.Namespace == nil && route.Namespace == gateway.Namespace: + // Route parent implicitly references gateway with same name in local namespace + gatewayWithRoutes.HTTPRoutes = append(gatewayWithRoutes.HTTPRoutes, route) + } + } + } + + for _, route := range tcpRoutes.Items { + for _, ref := range route.Spec.ParentRefs { + switch { + case string(ref.Name) != gateway.Name: + // Route parent references gateway with different name + continue + case ref.Namespace != nil && string(*ref.Namespace) == gateway.Namespace: + // Route parent explicitly references gateway with same name and namespace + gatewayWithRoutes.TCPRoutes = append(gatewayWithRoutes.TCPRoutes, route) + case ref.Namespace == nil && route.Namespace == gateway.Namespace: + // Route parent implicitly references gateway with same name in local namespace + gatewayWithRoutes.TCPRoutes = append(gatewayWithRoutes.TCPRoutes, route) + } + } + } + + switch strings.ToLower(c.flagOutput) { + case "json": + if err := c.writeJSONOutput(gatewayWithRoutes); err != nil { + return fmt.Errorf("error writing CRDs as JSON: %w", err) + } + default: + file, err := os.Create(fmt.Sprintf("./%s.zip", c.gatewayName)) + if err != nil { + return fmt.Errorf("error creating output file: %w", err) + } + + zipw := zip.NewWriter(file) + defer zipw.Close() + + if err := c.writeArchive(zipw, c.gatewayName+".yaml", gatewayWithRoutes); err != nil { + return fmt.Errorf("error writing CRDs to zip archive: %w", err) + } + return zipw.Close() + } + + return nil +} + +func (c *Command) writeJSONOutput(obj interface{}) error { + output, err := json.MarshalIndent(obj, "", "\t") + if err != nil { + return err + } + + c.UI.Output(string(output)) + return nil +} + +func (c *Command) writeArchive(zipw *zip.Writer, name string, obj interface{}) error { + w, err := zipw.Create(name) + if err != nil { + return fmt.Errorf("error creating zip entry for %s: %w", name, err) + } + + objYaml, err := yaml.Marshal(obj) + if err != nil { + return fmt.Errorf("error marshalling %s: %w", name, err) + } + + _, err = w.Write(objYaml) + if err != nil { + return fmt.Errorf("error writing %s to zip archive: %w", name, err) + } + + c.UI.Output("Wrote to zip archive " + name) + + return nil +} + +// initKubernetes initializes the REST config and uses it to initialize the k8s client. +func (c *Command) initKubernetes() (err error) { + settings := helmcli.New() + + // If a kubeconfig was specified, use it + if c.flagKubeConfig != "" { + settings.KubeConfig = c.flagKubeConfig + } + + // If a kube context was specified, use it + if c.flagKubeContext != "" { + settings.KubeContext = c.flagKubeContext + } + + // Create a REST config from the settings for our Kubernetes client + if c.restConfig == nil { + if c.restConfig, err = settings.RESTClientGetter().ToRESTConfig(); err != nil { + return fmt.Errorf("error creating Kubernetes REST config: %w", err) + } + } + + // Create a controller-runtime client from c.restConfig + if c.kubernetes == nil { + if c.kubernetes, err = client.New(c.restConfig, client.Options{}); err != nil { + return fmt.Errorf("error creating controller-runtime client: %w", err) + } + // FUTURE Fix dependency discrepancies between modules in this repo so that this scheme can be added (see above) + //_ = v1alpha1.AddToScheme(c.kubernetes.Scheme()) + _ = gwv1alpha2.AddToScheme(c.kubernetes.Scheme()) + _ = gwv1beta1.AddToScheme(c.kubernetes.Scheme()) + } + + // If no namespace was specified, use the one from the kube context + if c.flagGatewayNamespace == "" { + if c.flagOutput != "json" { + c.UI.Output("No namespace specified, using current kube context namespace: %s", settings.Namespace()) + } + c.flagGatewayNamespace = settings.Namespace() + } + + return nil +} diff --git a/cli/cmd/gateway/read/command_test.go b/cli/cmd/gateway/read/command_test.go new file mode 100644 index 0000000000..9c8ab59feb --- /dev/null +++ b/cli/cmd/gateway/read/command_test.go @@ -0,0 +1,157 @@ +package read + +import ( + "bytes" + "context" + "encoding/json" + "io" + "os" + "testing" + + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/cli/common" + "github.com/hashicorp/consul-k8s/cli/common/terminal" +) + +func TestFlagParsing(t *testing.T) { + cases := map[string]struct { + args []string + out int + }{ + "No args": { + args: []string{}, + out: 1, + }, + "Multiple gateway names passed": { + args: []string{"gateway-1", "gateway-2"}, + out: 1, + }, + "Nonexistent flag passed, -foo bar": { + args: []string{"gateway-1", "-foo", "bar"}, + out: 1, + }, + "Invalid argument passed, -namespace YOLO": { + args: []string{"gateway-1", "-namespace", "YOLO"}, + out: 1, + }, + "User passed incorrect output": { + args: []string{"gateway-1", "-output", "image"}, + out: 1, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + c := setupCommand(new(bytes.Buffer)) + c.kubernetes = fake.NewClientBuilder().WithObjectTracker(nil).Build() + + out := c.Run(tc.args) + require.Equal(t, tc.out, out) + }) + } +} + +func TestReadCommandOutput(t *testing.T) { + gatewayClassName := "gateway-class-1" + gatewayName := "gateway-1" + routeName := "route-1" + + fakeGatewayClass := &gwv1beta1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: gatewayClassName, + }, + } + + fakeGateway := &gwv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: gatewayName, + }, + Spec: gwv1beta1.GatewaySpec{ + GatewayClassName: gwv1beta1.ObjectName(gatewayClassName), + }, + } + + fakeHTTPRoute := &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: routeName, + }, + Spec: gwv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gwv1beta1.CommonRouteSpec{ + ParentRefs: []gwv1beta1.ParentReference{ + { + Name: gwv1beta1.ObjectName(fakeGateway.Name), + }, + }, + }, + }, + } + + fakeUnattachedHTTPRoute := &gwv1beta1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "route-2", + }, + } + + buf := new(bytes.Buffer) + c := setupCommand(buf) + + scheme := scheme.Scheme + gwv1beta1.AddToScheme(scheme) + gwv1alpha2.AddToScheme(scheme) + + c.kubernetes = fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(fakeGatewayClass, fakeGateway, fakeHTTPRoute, fakeUnattachedHTTPRoute). + Build() + + out := c.Run([]string{gatewayName, "-output", "json"}) + require.Equal(t, 0, out) + + gatewayWithRoutes := struct { + Gateway gwv1beta1.Gateway `json:"Gateway"` + GatewayClass gwv1beta1.GatewayClass `json:"GatewayClass"` + HTTPRoutes []gwv1beta1.HTTPRoute `json:"HTTPRoutes"` + }{} + require.NoErrorf(t, json.Unmarshal(buf.Bytes(), &gatewayWithRoutes), "failed to parse JSON output %s", buf.String()) + + // Make gateway assertions + assert.Equal(t, gatewayName, gatewayWithRoutes.Gateway.Name) + + // Make gateway class assertions + assert.Equal(t, gatewayClassName, gatewayWithRoutes.GatewayClass.Name) + + // Make http route assertions + require.Len(t, gatewayWithRoutes.HTTPRoutes, 1) + assert.Equal(t, routeName, gatewayWithRoutes.HTTPRoutes[0].Name) +} + +func setupCommand(buf io.Writer) *Command { + // Log at a test level to standard out. + log := hclog.New(&hclog.LoggerOptions{ + Name: "test", + Level: hclog.Debug, + Output: os.Stdout, + }) + + // Setup and initialize the command struct + command := &Command{ + BaseCommand: &common.BaseCommand{ + Log: log, + UI: terminal.NewUI(context.Background(), buf), + }, + } + command.init() + + return command +} diff --git a/cli/cmd/proxy/list/command.go b/cli/cmd/proxy/list/command.go index 0204832c44..7d7fe6e4a7 100644 --- a/cli/cmd/proxy/list/command.go +++ b/cli/cmd/proxy/list/command.go @@ -7,14 +7,17 @@ import ( "encoding/json" "errors" "fmt" + "sort" "strings" "sync" "github.com/posener/complete" + "golang.org/x/exp/maps" helmCLI "helm.sh/helm/v3/pkg/cli" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/validation" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" "github.com/hashicorp/consul-k8s/cli/common" @@ -206,66 +209,66 @@ func (c *ListCommand) namespace() string { } } -// fetchPods fetches all pods in flagNamespace which run Consul proxies. +// fetchPods fetches all pods in flagNamespace which run Consul proxies, +// making sure to return each pod only once even if multiple label selectors may +// return the same pod. The pods in the resulting list are grouped by proxy type +// and then sorted by namespace + name within each group. func (c *ListCommand) fetchPods() ([]v1.Pod, error) { - var pods []v1.Pod - - // Fetch all pods in the namespace with labels matching the gateway component names. - gatewaypods, err := c.kubernetes.CoreV1().Pods(c.namespace()).List(c.Ctx, metav1.ListOptions{ - LabelSelector: "component in (api-gateway, ingress-gateway, mesh-gateway, terminating-gateway), chart=consul-helm", - }) - if err != nil { - return nil, err + var ( + apiGateways = make(map[types.NamespacedName]v1.Pod) + ingressGateways = make(map[types.NamespacedName]v1.Pod) + meshGateways = make(map[types.NamespacedName]v1.Pod) + terminatingGateways = make(map[types.NamespacedName]v1.Pod) + sidecars = make(map[types.NamespacedName]v1.Pod) + ) + + // Map target map for each proxy type. Note that some proxy types + // require multiple selectors and thus target the same map. + proxySelectors := []struct { + Target map[types.NamespacedName]v1.Pod + Selector string + }{ + {Target: apiGateways, Selector: "component=api-gateway, gateway.consul.hashicorp.com/managed=true"}, + {Target: apiGateways, Selector: "api-gateway.consul.hashicorp.com/managed=true"}, // Legacy API gateways + {Target: ingressGateways, Selector: "component=ingress-gateway, chart=consul-helm"}, + {Target: meshGateways, Selector: "component=mesh-gateway, chart=consul-helm"}, + {Target: terminatingGateways, Selector: "component=terminating-gateway, chart=consul-helm"}, + {Target: sidecars, Selector: "consul.hashicorp.com/connect-inject-status=injected"}, } - pods = append(pods, gatewaypods.Items...) - // Fetch API Gateway pods with deprecated label and append if they aren't already in the list - // TODO this block can be deleted if and when we decide we are ok with no longer listing pods of people using previous API Gateway - // versions. - apigatewaypods, err := c.kubernetes.CoreV1().Pods(c.namespace()).List(c.Ctx, metav1.ListOptions{ - LabelSelector: "api-gateway.consul.hashicorp.com/managed=true", - }) + // Query all proxy types into their appropriate maps. + for _, selector := range proxySelectors { + pods, err := c.kubernetes.CoreV1().Pods(c.namespace()).List(c.Ctx, metav1.ListOptions{ + LabelSelector: selector.Selector, + }) + if err != nil { + return nil, err + } - namespacedName := func(pod v1.Pod) string { - return pod.Namespace + pod.Name - } - if err != nil { - return nil, err + for _, pod := range pods.Items { + name := types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name} + selector.Target[name] = pod + } } - if len(apigatewaypods.Items) > 0 { - //Deduplicated pod list - seenPods := map[string]struct{}{} - for _, pod := range apigatewaypods.Items { - if _, ok := seenPods[namespacedName(pod)]; ok { - continue - } - found := false - for _, gatewayPod := range gatewaypods.Items { - //note that we already have this pod in the list so we can exit early. - seenPods[namespacedName(gatewayPod)] = struct{}{} - - if (namespacedName(gatewayPod)) == namespacedName(pod) { - found = true - break - } - } - //pod isn't in the list already, we can add it. - if !found { - pods = append(pods, pod) - } - } + // Collect all proxies into a single list of Pods, ordered by proxy type. + // Within each proxy type subgroup, order by namespace and then name for output readability. + var pods []v1.Pod + var podSources = []map[types.NamespacedName]v1.Pod{ + apiGateways, ingressGateways, meshGateways, terminatingGateways, sidecars, } - //--- + for _, podSource := range podSources { + names := maps.Keys(podSource) - // Fetch all pods in the namespace with a label indicating they are a service networked by Consul. - sidecarpods, err := c.kubernetes.CoreV1().Pods(c.namespace()).List(c.Ctx, metav1.ListOptions{ - LabelSelector: "consul.hashicorp.com/connect-inject-status=injected", - }) - if err != nil { - return nil, err + // Insert Pods ordered by their NamespacedName which amounts to "/". + sort.SliceStable(names, func(i, j int) bool { + return strings.Compare(names[i].String(), names[j].String()) < 0 + }) + + for _, name := range names { + pods = append(pods, podSource[name]) + } } - pods = append(pods, sidecarpods.Items...) return pods, nil } @@ -281,12 +284,6 @@ func (c *ListCommand) output(pods []v1.Pod) { return } - if c.flagAllNamespaces { - c.UI.Output("Namespace: all namespaces\n") - } else { - c.UI.Output("Namespace: %s\n", c.namespace()) - } - var tbl *terminal.Table if c.flagAllNamespaces { tbl = terminal.NewTable("Namespace", "Name", "Type") @@ -297,7 +294,7 @@ func (c *ListCommand) output(pods []v1.Pod) { for _, pod := range pods { var proxyType string - // Get the type for ingress, mesh, and terminating gateways. + // Get the type for api, ingress, mesh, and terminating gateways + sidecars. switch pod.Labels["component"] { case "api-gateway": proxyType = "API Gateway" @@ -333,6 +330,10 @@ func (c *ListCommand) output(pods []v1.Pod) { c.UI.Output(string(jsonSt)) } } else { + if !c.flagAllNamespaces { + c.UI.Output("Namespace: %s\n", c.namespace()) + } + c.UI.Table(tbl) } diff --git a/cli/cmd/proxy/list/command_test.go b/cli/cmd/proxy/list/command_test.go index 5493ab88a9..d0ad84fa0b 100644 --- a/cli/cmd/proxy/list/command_test.go +++ b/cli/cmd/proxy/list/command_test.go @@ -6,15 +6,13 @@ package list import ( "bytes" "context" + "encoding/json" "flag" "fmt" "io" "os" "testing" - "github.com/hashicorp/consul-k8s/cli/common" - cmnFlag "github.com/hashicorp/consul-k8s/cli/common/flag" - "github.com/hashicorp/consul-k8s/cli/common/terminal" "github.com/hashicorp/go-hclog" "github.com/posener/complete" "github.com/stretchr/testify/assert" @@ -22,6 +20,10 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" + + "github.com/hashicorp/consul-k8s/cli/common" + cmnFlag "github.com/hashicorp/consul-k8s/cli/common/flag" + "github.com/hashicorp/consul-k8s/cli/common/terminal" ) func TestFlagParsing(t *testing.T) { @@ -229,6 +231,9 @@ func TestFetchPods(t *testing.T) { } } +// TestListCommandOutput tests the output of the list command. The output must +// contain the expected regular expressions and not contain the not expected +// regular expressions. func TestListCommandOutput(t *testing.T) { // These regular expressions must be present in the output. expected := []string{ @@ -340,11 +345,10 @@ func TestListCommandOutput(t *testing.T) { } } +// TestListCommandOutputInJsonFormat tests the output of the list command when +// the output format is set to JSON. The proxies must be presented in the appropriate +// order and format. func TestListCommandOutputInJsonFormat(t *testing.T) { - // These regular expressions must be present in the output. - expected := ".*Name.*api-gateway.*\n.*Namespace.*consul.*\n.*Type.*API Gateway.*\n.*\n.*\n.*Name.*both-labels-api-gateway.*\n.*Namespace.*consul.*\n.*Type.*API Gateway.*\n.*\n.*\n.*Name.*mesh-gateway.*\n.*Namespace.*consul.*\n.*Type.*Mesh Gateway.*\n.*\n.*\n.*Name.*terminating-gateway.*\n.*Namespace.*consul.*\n.*Type.*Terminating Gateway.*\n.*\n.*\n.*Name.*ingress-gateway.*\n.*Namespace.*default.*\n.*Type.*Ingress Gateway.*\n.*\n.*\n.*Name.*deprecated-api-gateway.*\n.*Namespace.*consul.*\n.*Type.*API Gateway.*\n.*\n.*\n.*Name.*pod1.*\n.*Namespace.*default.*\n.*Type.*Sidecar.*" - notExpected := "default.*dont-fetch.*Sidecar" - pods := []v1.Pod{ { ObjectMeta: metav1.ObjectMeta{ @@ -381,8 +385,8 @@ func TestListCommandOutputInJsonFormat(t *testing.T) { Name: "api-gateway", Namespace: "consul", Labels: map[string]string{ - "component": "api-gateway", - "chart": "consul-helm", + "component": "api-gateway", + "gateway.consul.hashicorp.com/managed": "true", }, }, }, @@ -432,12 +436,21 @@ func TestListCommandOutputInJsonFormat(t *testing.T) { out := c.Run([]string{"-A", "-o", "json"}) require.Equal(t, 0, out) - actual := buf.String() - - require.Regexp(t, expected, actual) - for _, expression := range notExpected { - require.NotRegexp(t, expression, actual) + var actual []struct { + Name string `json:"Name"` + Namespace string `json:"Namespace"` + Type string `json:"Type"` } + require.NoErrorf(t, json.Unmarshal(buf.Bytes(), &actual), "failed to parse json output: %s", buf.String()) + + require.Len(t, actual, 7) + assert.Equal(t, "api-gateway", actual[0].Name) + assert.Equal(t, "both-labels-api-gateway", actual[1].Name) + assert.Equal(t, "deprecated-api-gateway", actual[2].Name) + assert.Equal(t, "ingress-gateway", actual[3].Name) + assert.Equal(t, "mesh-gateway", actual[4].Name) + assert.Equal(t, "terminating-gateway", actual[5].Name) + assert.Equal(t, "pod1", actual[6].Name) } func TestNoPodsFound(t *testing.T) { diff --git a/cli/cmd/proxy/read/command.go b/cli/cmd/proxy/read/command.go index 26ca33b045..a5d188bfef 100644 --- a/cli/cmd/proxy/read/command.go +++ b/cli/cmd/proxy/read/command.go @@ -144,7 +144,7 @@ func (c *ReadCommand) init() { Default: -1, }) - f = c.set.NewSet("GlobalOptions") + f = c.set.NewSet("Global Options") f.StringVar(&flag.StringVar{ Name: flagNameKubeConfig, Aliases: []string{"c"}, diff --git a/cli/commands.go b/cli/commands.go index 2c2e797673..ca06d5d679 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -5,15 +5,20 @@ package main import ( "context" - "github.com/hashicorp/consul-k8s/cli/cmd/proxy/stats" + + "github.com/hashicorp/go-hclog" + "github.com/mitchellh/cli" "github.com/hashicorp/consul-k8s/cli/cmd/config" config_read "github.com/hashicorp/consul-k8s/cli/cmd/config/read" + gwlist "github.com/hashicorp/consul-k8s/cli/cmd/gateway/list" + gwread "github.com/hashicorp/consul-k8s/cli/cmd/gateway/read" "github.com/hashicorp/consul-k8s/cli/cmd/install" "github.com/hashicorp/consul-k8s/cli/cmd/proxy" "github.com/hashicorp/consul-k8s/cli/cmd/proxy/list" "github.com/hashicorp/consul-k8s/cli/cmd/proxy/loglevel" "github.com/hashicorp/consul-k8s/cli/cmd/proxy/read" + "github.com/hashicorp/consul-k8s/cli/cmd/proxy/stats" "github.com/hashicorp/consul-k8s/cli/cmd/status" "github.com/hashicorp/consul-k8s/cli/cmd/troubleshoot" troubleshoot_proxy "github.com/hashicorp/consul-k8s/cli/cmd/troubleshoot/proxy" @@ -24,8 +29,6 @@ import ( "github.com/hashicorp/consul-k8s/cli/common" "github.com/hashicorp/consul-k8s/cli/common/terminal" "github.com/hashicorp/consul-k8s/version" - "github.com/hashicorp/go-hclog" - "github.com/mitchellh/cli" ) func initializeCommands(ctx context.Context, log hclog.Logger) (*common.BaseCommand, map[string]cli.CommandFactory) { @@ -62,6 +65,16 @@ func initializeCommands(ctx context.Context, log hclog.Logger) (*common.BaseComm Version: version.GetHumanVersion(), }, nil }, + "gateway list": func() (cli.Command, error) { + return &gwlist.Command{ + BaseCommand: baseCommand, + }, nil + }, + "gateway read": func() (cli.Command, error) { + return &gwread.Command{ + BaseCommand: baseCommand, + }, nil + }, "proxy": func() (cli.Command, error) { return &proxy.ProxyCommand{ BaseCommand: baseCommand, diff --git a/cli/go.mod b/cli/go.mod index f97cda4aef..287f24513a 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -1,8 +1,8 @@ module github.com/hashicorp/consul-k8s/cli -go 1.22 +go 1.22.0 -toolchain go1.22.5 +toolchain go1.22.6 replace github.com/hashicorp/consul-k8s/version => ../version @@ -13,7 +13,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/hashicorp/consul-k8s/charts v0.0.0-00010101000000-000000000000 github.com/hashicorp/consul-k8s/version v0.0.0 - github.com/hashicorp/consul/troubleshoot v0.7.1 + github.com/hashicorp/consul/troubleshoot v0.7.4 github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/hcp-sdk-go v0.62.1-0.20230913154003-cf69c0370c54 github.com/kr/text v0.2.0 @@ -21,19 +21,23 @@ require ( github.com/mitchellh/cli v1.1.5 github.com/olekukonko/tablewriter v0.0.5 github.com/posener/complete v1.2.3 - github.com/stretchr/testify v1.8.4 - golang.org/x/text v0.17.0 + github.com/stretchr/testify v1.9.0 + golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 + golang.org/x/text v0.21.0 helm.sh/helm/v3 v3.14.4 - k8s.io/api v0.29.0 - k8s.io/apiextensions-apiserver v0.29.0 - k8s.io/apimachinery v0.29.0 + k8s.io/api v0.29.2 + k8s.io/apiextensions-apiserver v0.29.2 + k8s.io/apimachinery v0.29.2 k8s.io/cli-runtime v0.29.0 - k8s.io/client-go v0.29.0 - k8s.io/utils v0.0.0-20230726121419-3b25d923346b - sigs.k8s.io/yaml v1.3.0 + k8s.io/client-go v0.29.2 + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 + sigs.k8s.io/controller-runtime v0.17.6 + sigs.k8s.io/gateway-api v0.7.1 + sigs.k8s.io/yaml v1.4.0 ) require ( + cel.dev/expr v0.15.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/BurntSushi/toml v1.3.2 // indirect @@ -48,9 +52,9 @@ require ( github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // indirect + github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect github.com/containerd/containerd v1.7.13 // indirect github.com/containerd/log v0.1.0 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect @@ -62,26 +66,27 @@ require ( github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.0 // indirect github.com/envoyproxy/go-control-plane v0.12.0 // indirect github.com/envoyproxy/go-control-plane/xdsmatcher v0.0.0-20230524161521-aaaacbfbe53e // indirect - github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect + github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect github.com/evanphx/json-patch v5.7.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect - github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect - github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/logr v1.4.2 // 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 - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/loads v0.21.2 // indirect github.com/go-openapi/runtime v0.25.0 // indirect github.com/go-openapi/spec v0.20.8 // indirect github.com/go-openapi/strfmt v0.21.3 // indirect - github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/validate v0.22.1 // indirect github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect github.com/gobwas/glob v0.2.3 // indirect @@ -91,23 +96,23 @@ require ( github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.0 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/gorilla/websocket v1.5.1 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect - github.com/hashicorp/consul/api v1.29.4 // indirect - github.com/hashicorp/consul/envoyextensions v0.7.3 // indirect + github.com/hashicorp/consul/api v1.31.0 // indirect + github.com/hashicorp/consul/envoyextensions v0.7.7 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-version v1.2.1 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/serf v0.10.1 // indirect github.com/huandu/xstrings v1.4.0 // indirect - github.com/imdario/mergo v0.3.13 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -120,7 +125,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/miekg/dns v1.1.58 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -128,7 +133,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect - github.com/moby/spdystream v0.2.0 // indirect + github.com/moby/spdystream v0.4.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -142,50 +147,47 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.16.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.44.0 // indirect - github.com/prometheus/procfs v0.10.1 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/rubenv/sql-migrate v1.5.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect go.mongodb.org/mongo-driver v1.11.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect - go.opentelemetry.io/otel v1.19.0 // indirect - go.opentelemetry.io/otel/metric v1.19.0 // indirect - go.opentelemetry.io/otel/trace v1.19.0 // indirect - go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect - golang.org/x/crypto v0.26.0 // indirect - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/time v0.3.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiserver v0.29.0 // indirect - k8s.io/component-base v0.29.0 // indirect - k8s.io/klog/v2 v2.110.1 // indirect - k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/apiserver v0.29.2 // indirect + k8s.io/component-base v0.29.2 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108 // indirect k8s.io/kubectl v0.29.0 // indirect oras.land/oras-go v1.2.5 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect diff --git a/cli/go.sum b/cli/go.sum index 9edb6c903b..6c10803c44 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -1,3 +1,5 @@ +cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w= +cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= @@ -66,8 +68,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -76,8 +78,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw= +github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/containerd v1.7.13 h1:wPYKIeGMN8vaggSKuV1X0wZulpMz4CrgEsZdaCyB6Is= @@ -86,7 +88,7 @@ github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= @@ -116,19 +118,20 @@ github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= +github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI= github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= github.com/envoyproxy/go-control-plane/xdsmatcher v0.0.0-20230524161521-aaaacbfbe53e h1:g8euodkL4GdSpVAjfzhssb07KgVmOUqyF4QOmwFumTs= github.com/envoyproxy/go-control-plane/xdsmatcher v0.0.0-20230524161521-aaaacbfbe53e/go.mod h1:/NGEcKqwNq3HAS2vCqHfsPx9sJZbkiNQ6dGx9gTE/NA= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= -github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -136,8 +139,8 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= @@ -151,10 +154,12 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc= github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo= @@ -165,12 +170,12 @@ github.com/go-openapi/errors v0.20.3 h1:rz6kiC84sqNQoqrtulzaL/VERgkoCyB6WdEkc2uj github.com/go-openapi/errors v0.20.3/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g= github.com/go-openapi/loads v0.21.2 h1:r2a/xFIYeZ4Qd2TnGpWDIQNcP80dIaZgf704za8enro= github.com/go-openapi/loads v0.21.2/go.mod h1:Jq58Os6SSGz0rzh62ptiu8Z31I+OTHqmULx5e/gJbNw= @@ -187,8 +192,8 @@ github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqb github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/validate v0.22.1 h1:G+c2ub6q47kfX1sOBLwIQwzBVt8qmOAARyo/9Fqs9NU= github.com/go-openapi/validate v0.22.1/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE= @@ -275,29 +280,26 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/hashicorp/consul/api v1.29.4 h1:P6slzxDLBOxUSj3fWo2o65VuKtbtOXFi7TSSgtXutuE= -github.com/hashicorp/consul/api v1.29.4/go.mod h1:HUlfw+l2Zy68ceJavv2zAyArl2fqhGWnMycyt56sBgg= -github.com/hashicorp/consul/envoyextensions v0.7.3 h1:5Gn1Hj135NYNRBmB3IdwhkxIHQgEJPjXYPZcA+05rNY= -github.com/hashicorp/consul/envoyextensions v0.7.3/go.mod h1:tya/kHsOBGaeAS9inAfUFJIEJ812c125cQD4MrLTt2s= -github.com/hashicorp/consul/proto-public v0.6.2 h1:+DA/3g/IiKlJZb88NBn0ZgXrxJp2NlvCZdEyl+qxvL0= -github.com/hashicorp/consul/proto-public v0.6.2/go.mod h1:cXXbOg74KBNGajC+o8RlA502Esf0R9prcoJgiOX/2Tg= +github.com/hashicorp/consul/api v1.31.0 h1:32BUNLembeSRek0G/ZAM6WNfdEwYdYo8oQ4+JoqGkNQ= +github.com/hashicorp/consul/api v1.31.0/go.mod h1:2ZGIiXM3A610NmDULmCHd/aqBJj8CkMfOhswhOafxRg= +github.com/hashicorp/consul/envoyextensions v0.7.7 h1:4vTkYLsmknHqtzw3Zf74FJbLeg5flIjIN77A6FBVBUA= +github.com/hashicorp/consul/envoyextensions v0.7.7/go.mod h1:tOlLYi2UgCDg4x6qhe+O9Kz4o6SQ7E8fRFxCUKH4pkk= github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg= github.com/hashicorp/consul/sdk v0.16.1/go.mod h1:fSXvwxB2hmh1FMZCNl6PwX0Q/1wdWtHJcZ7Ea5tns0s= -github.com/hashicorp/consul/troubleshoot v0.7.1 h1:IQYxC1qsV3jO74VZDyPi283Ufi84/mXSMm53U8dsN2M= -github.com/hashicorp/consul/troubleshoot v0.7.1/go.mod h1:U+fpb8yE3iGJTahAY1VGda4aYUDhaa0IZu+sIgGvcwk= +github.com/hashicorp/consul/troubleshoot v0.7.4 h1:b1GqnBrQ739L+Xcz28gKxIVOigFcgBxOaYj1Jtl4FUA= +github.com/hashicorp/consul/troubleshoot v0.7.4/go.mod h1:haNIaNQtUzxJ4Z6YEywctQ9gcACacnK7UbEwMRs8TCg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -327,8 +329,8 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= -github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= @@ -346,8 +348,8 @@ github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -375,7 +377,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -424,11 +425,10 @@ github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= +github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng= github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4= @@ -451,8 +451,8 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= +github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= @@ -477,10 +477,10 @@ github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= -github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= -github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= -github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY= +github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU= @@ -511,30 +511,30 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= -github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -559,16 +559,16 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -578,9 +578,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= @@ -615,20 +614,24 @@ go.mongodb.org/mongo-driver v1.11.1 h1:QP0znIRTuL0jf1oBQoAoM0C6ZJfBK4kx0Uumtv1A7 go.mongodb.org/mongo-driver v1.11.1/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= -go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= -go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= -go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= -go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= -go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= -go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= @@ -641,11 +644,11 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -660,7 +663,6 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -672,11 +674,11 @@ golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -687,8 +689,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -725,14 +727,14 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -740,10 +742,10 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -766,23 +768,18 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g= -google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= -google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e h1:z3vDksarJxsAKM5dmEGv0GHwE2hKJ096wZra71Vs4sw= -google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -791,8 +788,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -815,7 +812,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= @@ -824,30 +820,34 @@ helm.sh/helm/v3 v3.14.4 h1:6FSpEfqyDalHq3kUr4gOMThhgY55kXUEjdQoyODYnrM= helm.sh/helm/v3 v3.14.4/go.mod h1:Tje7LL4gprZpuBNTbG34d1Xn5NmRT3OWfBRwpOSer9I= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= -k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= -k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= -k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= -k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= -k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= -k8s.io/apiserver v0.29.0 h1:Y1xEMjJkP+BIi0GSEv1BBrf1jLU9UPfAnnGGbbDdp7o= -k8s.io/apiserver v0.29.0/go.mod h1:31n78PsRKPmfpee7/l9NYEv67u6hOL6AfcE761HapDM= +k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A= +k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0= +k8s.io/apiextensions-apiserver v0.29.2 h1:UK3xB5lOWSnhaCk0RFZ0LUacPZz9RY4wi/yt2Iu+btg= +k8s.io/apiextensions-apiserver v0.29.2/go.mod h1:aLfYjpA5p3OwtqNXQFkhJ56TB+spV8Gc4wfMhUA3/b8= +k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= +k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= +k8s.io/apiserver v0.29.2 h1:+Z9S0dSNr+CjnVXQePG8TcBWHr3Q7BmAr7NraHvsMiQ= +k8s.io/apiserver v0.29.2/go.mod h1:B0LieKVoyU7ykQvPFm7XSdIHaCHSzCzQWPFa5bqbeMQ= k8s.io/cli-runtime v0.29.0 h1:q2kC3cex4rOBLfPOnMSzV2BIrrQlx97gxHJs21KxKS4= k8s.io/cli-runtime v0.29.0/go.mod h1:VKudXp3X7wR45L+nER85YUzOQIru28HQpXr0mTdeCrk= -k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= -k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= -k8s.io/component-base v0.29.0 h1:T7rjd5wvLnPBV1vC4zWd/iWRbV8Mdxs+nGaoaFzGw3s= -k8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M= -k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= -k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/client-go v0.29.2 h1:FEg85el1TeZp+/vYJM7hkDlSTFZ+c5nnK44DJ4FyoRg= +k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA= +k8s.io/component-base v0.29.2 h1:lpiLyuvPA9yV1aQwGLENYyK7n/8t6l3nn3zAtFTJYe8= +k8s.io/component-base v0.29.2/go.mod h1:BfB3SLrefbZXiBfbM+2H1dlat21Uewg/5qtKOl8degM= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108 h1:Q8Z7VlGhcJgBHJHYugJ/K/7iB8a2eSxCyxdVjJp+lLY= +k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/kubectl v0.29.0 h1:Oqi48gXjikDhrBF67AYuZRTcJV4lg2l42GmvsP7FmYI= k8s.io/kubectl v0.29.0/go.mod h1:0jMjGWIcMIQzmUaMgAzhSELv5WtHo2a8pq67DtviAJs= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= +sigs.k8s.io/controller-runtime v0.17.6 h1:12IXsozEsIXWAMRpgRlYS1jjAHQXHtWEOMdULh3DbEw= +sigs.k8s.io/controller-runtime v0.17.6/go.mod h1:N0jpP5Lo7lMTF9aL56Z/B2oWBJjey6StQM0jRbKQXtY= +sigs.k8s.io/gateway-api v0.7.1 h1:Tts2jeepVkPA5rVG/iO+S43s9n7Vp7jCDhZDQYtPigQ= +sigs.k8s.io/gateway-api v0.7.1/go.mod h1:Xv0+ZMxX0lu1nSSDIIPEfbVztgNZ+3cfiYrJsa2Ooso= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= @@ -856,5 +856,5 @@ sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/control-plane/api-gateway/common/helm_config.go b/control-plane/api-gateway/common/helm_config.go index d551757c5b..175cef56ba 100644 --- a/control-plane/api-gateway/common/helm_config.go +++ b/control-plane/api-gateway/common/helm_config.go @@ -19,6 +19,8 @@ type HelmConfig struct { ImageDataplane string // ImageConsulK8S is the Consul Kubernetes Control Plane image to use in gateway deployments. ImageConsulK8S string + // ImagePullSecrets reference one or more Secret(s) that contain the credentials to pull images from private image repos. + ImagePullSecrets []v1.LocalObjectReference // GlobalImagePullPolicy is the pull policy to use for all images used in gateway deployments. GlobalImagePullPolicy string ConsulDestinationNamespace string diff --git a/control-plane/api-gateway/gatekeeper/gatekeeper.go b/control-plane/api-gateway/gatekeeper/gatekeeper.go index 538444303f..79766219dc 100644 --- a/control-plane/api-gateway/gatekeeper/gatekeeper.go +++ b/control-plane/api-gateway/gatekeeper/gatekeeper.go @@ -106,7 +106,9 @@ func (g *Gatekeeper) namespacedName(gateway gwv1beta1.Gateway) types.NamespacedN } func (g *Gatekeeper) serviceAccountName(gateway gwv1beta1.Gateway, config common.HelmConfig) string { - if config.AuthMethod == "" && !config.EnableOpenShift { + // We only create a ServiceAccount if it's needed for RBAC or image pull secrets; + // otherwise, we clean up if one was previously created. + if config.AuthMethod == "" && !config.EnableOpenShift && len(config.ImagePullSecrets) == 0 { return "" } return gateway.Name diff --git a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go index f6342ec725..0c60aa610b 100644 --- a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go +++ b/control-plane/api-gateway/gatekeeper/gatekeeper_test.go @@ -197,12 +197,13 @@ func TestUpsert(t *testing.T) { }, }, helmConfig: common.HelmConfig{ - ImageDataplane: dataplaneImage, + ImageDataplane: dataplaneImage, + ImagePullSecrets: []corev1.LocalObjectReference{{Name: "my-secret"}}, }, initialResources: resources{}, finalResources: resources{ deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), + configureDeployment(name, namespace, labels, 3, nil, nil, name, "1"), }, roles: []*rbac.Role{}, secrets: []*corev1.Secret{ @@ -224,7 +225,9 @@ func TestUpsert(t *testing.T) { }, }, "1", false, false), }, - serviceAccounts: []*corev1.ServiceAccount{}, + serviceAccounts: []*corev1.ServiceAccount{ + configureServiceAccount(name, namespace, labels, "1", []corev1.LocalObjectReference{{Name: "my-secret"}}), + }, }, }, "create a new gateway deployment with managed Service": { @@ -279,7 +282,6 @@ func TestUpsert(t *testing.T) { }, }, "1", false, false), }, - serviceAccounts: []*corev1.ServiceAccount{}, }, }, "create a new gateway deployment with managed Service and ACLs": { @@ -307,13 +309,14 @@ func TestUpsert(t *testing.T) { }, }, helmConfig: common.HelmConfig{ - AuthMethod: "method", - ImageDataplane: dataplaneImage, + AuthMethod: "method", + ImageDataplane: dataplaneImage, + ImagePullSecrets: []corev1.LocalObjectReference{{Name: "my-secret"}}, }, initialResources: resources{}, finalResources: resources{ deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), + configureDeployment(name, namespace, labels, 3, nil, nil, name, "1"), }, roles: []*rbac.Role{ configureRole(name, namespace, labels, "1", false), @@ -341,7 +344,7 @@ func TestUpsert(t *testing.T) { }, "1", false, false), }, serviceAccounts: []*corev1.ServiceAccount{ - configureServiceAccount(name, namespace, labels, "1"), + configureServiceAccount(name, namespace, labels, "1", []corev1.LocalObjectReference{{Name: "my-secret"}}), }, }, }, @@ -451,7 +454,7 @@ func TestUpsert(t *testing.T) { }, initialResources: resources{ deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), + configureDeployment(name, namespace, labels, 3, nil, nil, name, "1"), }, roles: []*rbac.Role{ configureRole(name, namespace, labels, "1", false), @@ -472,12 +475,12 @@ func TestUpsert(t *testing.T) { }, "1", true, false), }, serviceAccounts: []*corev1.ServiceAccount{ - configureServiceAccount(name, namespace, labels, "1"), + configureServiceAccount(name, namespace, labels, "1", nil), }, }, finalResources: resources{ deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "2"), + configureDeployment(name, namespace, labels, 3, nil, nil, name, "2"), }, roles: []*rbac.Role{ configureRole(name, namespace, labels, "1", false), @@ -505,7 +508,7 @@ func TestUpsert(t *testing.T) { }, "2", false, false), }, serviceAccounts: []*corev1.ServiceAccount{ - configureServiceAccount(name, namespace, labels, "1"), + configureServiceAccount(name, namespace, labels, "1", nil), }, }, ignoreTimestampOnService: true, @@ -542,7 +545,7 @@ func TestUpsert(t *testing.T) { }, initialResources: resources{ deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), + configureDeployment(name, namespace, labels, 3, nil, nil, name, "1"), }, roles: []*rbac.Role{ configureRole(name, namespace, labels, "1", false), @@ -568,12 +571,12 @@ func TestUpsert(t *testing.T) { }, "1", true, false), }, serviceAccounts: []*corev1.ServiceAccount{ - configureServiceAccount(name, namespace, labels, "1"), + configureServiceAccount(name, namespace, labels, "1", nil), }, }, finalResources: resources{ deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "2"), + configureDeployment(name, namespace, labels, 3, nil, nil, name, "2"), }, roles: []*rbac.Role{ configureRole(name, namespace, labels, "1", false), @@ -595,7 +598,7 @@ func TestUpsert(t *testing.T) { }, "2", false, false), }, serviceAccounts: []*corev1.ServiceAccount{ - configureServiceAccount(name, namespace, labels, "1"), + configureServiceAccount(name, namespace, labels, "1", nil), }, }, ignoreTimestampOnService: true, @@ -955,7 +958,7 @@ func TestUpsert(t *testing.T) { }, finalResources: resources{ deployments: []*appsv1.Deployment{ - configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"), + configureDeployment(name, namespace, labels, 3, nil, nil, name, "1"), }, roles: []*rbac.Role{ configureRole(name, namespace, labels, "1", true), @@ -966,7 +969,7 @@ func TestUpsert(t *testing.T) { secrets: []*corev1.Secret{}, services: []*corev1.Service{}, serviceAccounts: []*corev1.ServiceAccount{ - configureServiceAccount(name, namespace, labels, "1"), + configureServiceAccount(name, namespace, labels, "1", nil), }, }, }, @@ -1311,7 +1314,7 @@ func TestDelete(t *testing.T) { }, "1", true, false), }, serviceAccounts: []*corev1.ServiceAccount{ - configureServiceAccount(name, namespace, labels, "1"), + configureServiceAccount(name, namespace, labels, "1", nil), }, }, finalResources: resources{ @@ -1377,7 +1380,7 @@ func TestDelete(t *testing.T) { }, "1", true, false), }, serviceAccounts: []*corev1.ServiceAccount{ - configureServiceAccount(name, namespace, labels, "1"), + configureServiceAccount(name, namespace, labels, "1", nil), }, }, finalResources: resources{ @@ -1475,6 +1478,9 @@ func validateResourcesExist(t *testing.T, client client.Client, helmConfig commo require.Equal(t, expected.Spec.Template.ObjectMeta.Annotations, actual.Spec.Template.ObjectMeta.Annotations) require.Equal(t, expected.Spec.Template.ObjectMeta.Labels, actual.Spec.Template.Labels) + // Ensure the service account is assigned + require.Equal(t, expected.Spec.Template.Spec.ServiceAccountName, actual.Spec.Template.Spec.ServiceAccountName) + // Ensure there is an init container hasInitContainer := false for _, container := range actual.Spec.Template.Spec.InitContainers { @@ -1684,7 +1690,7 @@ func validateResourcesAreDeleted(t *testing.T, k8sClient client.Client, resource return nil } -func configureDeployment(name, namespace string, labels map[string]string, replicas int32, nodeSelector map[string]string, tolerations []corev1.Toleration, serviceAccoutName, resourceVersion string) *appsv1.Deployment { +func configureDeployment(name, namespace string, labels map[string]string, replicas int32, nodeSelector map[string]string, tolerations []corev1.Toleration, serviceAccountName, resourceVersion string) *appsv1.Deployment { return &appsv1.Deployment{ TypeMeta: metav1.TypeMeta{ APIVersion: "apps/v1", @@ -1737,7 +1743,7 @@ func configureDeployment(name, namespace string, labels map[string]string, repli }, NodeSelector: nodeSelector, Tolerations: tolerations, - ServiceAccountName: serviceAccoutName, + ServiceAccountName: serviceAccountName, }, }, }, @@ -1886,7 +1892,7 @@ func configureService(name, namespace string, labels, annotations map[string]str return &service } -func configureServiceAccount(name, namespace string, labels map[string]string, resourceVersion string) *corev1.ServiceAccount { +func configureServiceAccount(name, namespace string, labels map[string]string, resourceVersion string, pullSecrets []corev1.LocalObjectReference) *corev1.ServiceAccount { return &corev1.ServiceAccount{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", @@ -1907,6 +1913,7 @@ func configureServiceAccount(name, namespace string, labels map[string]string, r }, }, }, + ImagePullSecrets: pullSecrets, } } diff --git a/control-plane/api-gateway/gatekeeper/init.go b/control-plane/api-gateway/gatekeeper/init.go index 6502b7be8f..e8a17dc8ea 100644 --- a/control-plane/api-gateway/gatekeeper/init.go +++ b/control-plane/api-gateway/gatekeeper/init.go @@ -7,12 +7,12 @@ import ( "bytes" "context" "fmt" - "k8s.io/utils/ptr" "strconv" "strings" "text/template" corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" @@ -36,9 +36,9 @@ type initContainerCommandData struct { LogJSON bool } -// containerInit returns the init container spec for connect-init that polls for the service and the connect proxy service to be registered +// initContainer returns the init container spec for connect-init that polls for the service and the connect proxy service to be registered // so that it can save the proxy service id to the shared volume and boostrap Envoy with the proxy-id. -func (g Gatekeeper) initContainer(config common.HelmConfig, name, namespace string) (corev1.Container, error) { +func (g *Gatekeeper) initContainer(config common.HelmConfig, name, namespace string) (corev1.Container, error) { data := initContainerCommandData{ AuthMethod: config.AuthMethod, LogLevel: config.LogLevel, diff --git a/control-plane/api-gateway/gatekeeper/rolebinding.go b/control-plane/api-gateway/gatekeeper/rolebinding.go index 1a60e752c8..f315b78402 100644 --- a/control-plane/api-gateway/gatekeeper/rolebinding.go +++ b/control-plane/api-gateway/gatekeeper/rolebinding.go @@ -10,12 +10,13 @@ import ( "k8s.io/apimachinery/pkg/types" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" rbac "k8s.io/api/rbac/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" + + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) func (g *Gatekeeper) upsertRoleBinding(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) error { @@ -65,7 +66,7 @@ func (g *Gatekeeper) deleteRoleBinding(ctx context.Context, gwName types.Namespa func (g *Gatekeeper) roleBinding(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) *rbac.RoleBinding { // Create resources for reference. This avoids bugs if naming patterns change. - serviceAccount := g.serviceAccount(gateway) + serviceAccount := g.serviceAccount(gateway, config) role := g.role(gateway, gcc, config) return &rbac.RoleBinding{ diff --git a/control-plane/api-gateway/gatekeeper/serviceaccount.go b/control-plane/api-gateway/gatekeeper/serviceaccount.go index d1c5c9883a..64dc0b75dd 100644 --- a/control-plane/api-gateway/gatekeeper/serviceaccount.go +++ b/control-plane/api-gateway/gatekeeper/serviceaccount.go @@ -7,18 +7,20 @@ import ( "context" "errors" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "k8s.io/apimachinery/pkg/types" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" + gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" ) func (g *Gatekeeper) upsertServiceAccount(ctx context.Context, gateway gwv1beta1.Gateway, config common.HelmConfig) error { - if config.AuthMethod == "" && !config.EnableOpenShift { + // We only create a ServiceAccount if it's needed for RBAC or image pull secrets; + // otherwise, we clean up if one was previously created. + if config.AuthMethod == "" && !config.EnableOpenShift && len(config.ImagePullSecrets) == 0 { return g.deleteServiceAccount(ctx, types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}) } @@ -47,15 +49,12 @@ func (g *Gatekeeper) upsertServiceAccount(ctx context.Context, gateway gwv1beta1 } // Create the ServiceAccount. - serviceAccount = g.serviceAccount(gateway) + serviceAccount = g.serviceAccount(gateway, config) if err := ctrl.SetControllerReference(&gateway, serviceAccount, g.Client.Scheme()); err != nil { return err } - if err := g.Client.Create(ctx, serviceAccount); err != nil { - return err - } - return nil + return g.Client.Create(ctx, serviceAccount) } func (g *Gatekeeper) deleteServiceAccount(ctx context.Context, gwName types.NamespacedName) error { @@ -69,12 +68,13 @@ func (g *Gatekeeper) deleteServiceAccount(ctx context.Context, gwName types.Name return nil } -func (g *Gatekeeper) serviceAccount(gateway gwv1beta1.Gateway) *corev1.ServiceAccount { +func (g *Gatekeeper) serviceAccount(gateway gwv1beta1.Gateway, config common.HelmConfig) *corev1.ServiceAccount { return &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: gateway.Name, Namespace: gateway.Namespace, Labels: common.LabelsForGateway(&gateway), }, + ImagePullSecrets: config.ImagePullSecrets, } } diff --git a/control-plane/api/v1alpha1/mesh_types.go b/control-plane/api/v1alpha1/mesh_types.go index 4d8a14358b..f70167a912 100644 --- a/control-plane/api/v1alpha1/mesh_types.go +++ b/control-plane/api/v1alpha1/mesh_types.go @@ -87,7 +87,18 @@ type MeshTLSConfig struct { } type MeshHTTPConfig struct { - SanitizeXForwardedClientCert bool `json:"sanitizeXForwardedClientCert"` + SanitizeXForwardedClientCert bool `json:"sanitizeXForwardedClientCert,omitempty"` + // Incoming configures settings for incoming HTTP traffic to mesh proxies. + Incoming *MeshDirectionalHTTPConfig `json:"incoming,omitempty"` + // There is not currently an outgoing MeshDirectionalHTTPConfig, as + // the only required config for either direction at present is inbound + // request normalization. +} + +// MeshDirectionalHTTPConfig holds mesh configuration specific to HTTP +// requests for a given traffic direction. +type MeshDirectionalHTTPConfig struct { + RequestNormalization *RequestNormalizationMeshConfig `json:"requestNormalization,omitempty"` } type PeeringMeshConfig struct { @@ -117,6 +128,61 @@ type MeshDirectionalTLSConfig struct { CipherSuites []string `json:"cipherSuites,omitempty"` } +// RequestNormalizationMeshConfig contains options pertaining to the +// normalization of HTTP requests processed by mesh proxies. +type RequestNormalizationMeshConfig struct { + // InsecureDisablePathNormalization sets the value of the \`normalize_path\` option in the Envoy listener's + // `HttpConnectionManager`. The default value is \`false\`. When set to \`true\` in Consul, \`normalize_path\` is + // set to \`false\` for the Envoy proxy. This parameter disables the normalization of request URL paths according to + // RFC 3986, conversion of \`\\\` to \`/\`, and decoding non-reserved %-encoded characters. When using L7 intentions + // with path match rules, we recommend enabling path normalization in order to avoid match rule circumvention with + // non-normalized path values. + InsecureDisablePathNormalization bool `json:"insecureDisablePathNormalization,omitempty"` + // MergeSlashes sets the value of the \`merge_slashes\` option in the Envoy listener's \`HttpConnectionManager\`. + // The default value is \`false\`. This option controls the normalization of request URL paths by merging + // consecutive \`/\` characters. This normalization is not part of RFC 3986. When using L7 intentions with path + // match rules, we recommend enabling this setting to avoid match rule circumvention through non-normalized path + // values, unless legitimate service traffic depends on allowing for repeat \`/\` characters, or upstream services + // are configured to differentiate between single and multiple slashes. + MergeSlashes bool `json:"mergeSlashes,omitempty"` + // PathWithEscapedSlashesAction sets the value of the \`path_with_escaped_slashes_action\` option in the Envoy + // listener's \`HttpConnectionManager\`. The default value of this option is empty, which is equivalent to + // \`IMPLEMENTATION_SPECIFIC_DEFAULT\`. This parameter controls the action taken in response to request URL paths + // with escaped slashes in the path. When using L7 intentions with path match rules, we recommend enabling this + // setting to avoid match rule circumvention through non-normalized path values, unless legitimate service traffic + // depends on allowing for escaped \`/\` or \`\\\` characters, or upstream services are configured to differentiate + // between escaped and unescaped slashes. Refer to the Envoy documentation for more information on available + // options. + PathWithEscapedSlashesAction string `json:"pathWithEscapedSlashesAction,omitempty"` + // HeadersWithUnderscoresAction sets the value of the \`headers_with_underscores_action\` option in the Envoy + // listener's \`HttpConnectionManager\` under \`common_http_protocol_options\`. The default value of this option is + // empty, which is equivalent to \`ALLOW\`. Refer to the Envoy documentation for more information on available + // options. + HeadersWithUnderscoresAction string `json:"headersWithUnderscoresAction,omitempty"` +} + +// PathWithEscapedSlashesAction is an enum that defines the action to take when +// a request path contains escaped slashes. It mirrors exactly the set of options +// in Envoy's UriPathNormalizationOptions.PathWithEscapedSlashesAction enum. +// See github.com/envoyproxy/go-control-plane envoy_http_v3.HttpConnectionManager_PathWithEscapedSlashesAction. +const ( + PathWithEscapedSlashesActionDefault = "IMPLEMENTATION_SPECIFIC_DEFAULT" + PathWithEscapedSlashesActionKeep = "KEEP_UNCHANGED" + PathWithEscapedSlashesActionReject = "REJECT_REQUEST" + PathWithEscapedSlashesActionUnescapeAndRedirect = "UNESCAPE_AND_REDIRECT" + PathWithEscapedSlashesActionUnescapeAndForward = "UNESCAPE_AND_FORWARD" +) + +// HeadersWithUnderscoresAction is an enum that defines the action to take when +// a request contains headers with underscores. It mirrors exactly the set of +// options in Envoy's HttpProtocolOptions.HeadersWithUnderscoresAction enum. +// See github.com/envoyproxy/go-control-plane envoy_core_v3.HttpProtocolOptions_HeadersWithUnderscoresAction. +const ( + HeadersWithUnderscoresActionAllow = "ALLOW" + HeadersWithUnderscoresActionRejectRequest = "REJECT_REQUEST" + HeadersWithUnderscoresActionDropHeader = "DROP_HEADER" +) + func (in *TransparentProxyMeshConfig) toConsul() capi.TransparentProxyMeshConfig { return capi.TransparentProxyMeshConfig{MeshDestinationsOnly: in.MeshDestinationsOnly} } @@ -227,6 +293,11 @@ func (in *Mesh) Validate(consulMeta common.ConsulMeta) error { errs = append(errs, in.Spec.TLS.validate(path.Child("tls"))...) errs = append(errs, in.Spec.Peering.validate(path.Child("peering"), consulMeta.PartitionsEnabled, consulMeta.Partition)...) + if in.Spec.HTTP != nil && + in.Spec.HTTP.Incoming != nil && + in.Spec.HTTP.Incoming.RequestNormalization != nil { + errs = append(errs, in.Spec.HTTP.Incoming.RequestNormalization.validate(path.Child("http", "incoming", "requestNormalization"))...) + } if len(errs) > 0 { return apierrors.NewInvalid( @@ -252,6 +323,28 @@ func (in *MeshHTTPConfig) toConsul() *capi.MeshHTTPConfig { } return &capi.MeshHTTPConfig{ SanitizeXForwardedClientCert: in.SanitizeXForwardedClientCert, + Incoming: in.Incoming.toConsul(), + } +} + +func (in *MeshDirectionalHTTPConfig) toConsul() *capi.MeshDirectionalHTTPConfig { + if in == nil { + return nil + } + return &capi.MeshDirectionalHTTPConfig{ + RequestNormalization: in.RequestNormalization.toConsul(), + } +} + +func (in *RequestNormalizationMeshConfig) toConsul() *capi.RequestNormalizationMeshConfig { + if in == nil { + return nil + } + return &capi.RequestNormalizationMeshConfig{ + InsecureDisablePathNormalization: in.InsecureDisablePathNormalization, + MergeSlashes: in.MergeSlashes, + PathWithEscapedSlashesAction: in.PathWithEscapedSlashesAction, + HeadersWithUnderscoresAction: in.HeadersWithUnderscoresAction, } } @@ -316,6 +409,36 @@ func (in *PeeringMeshConfig) validate(path *field.Path, partitionsEnabled bool, return errs } +func (in *RequestNormalizationMeshConfig) validate(path *field.Path) field.ErrorList { + if in == nil { + return nil + } + + var errs field.ErrorList + pathWithEscapedSlashesActions := []string{ + PathWithEscapedSlashesActionDefault, + PathWithEscapedSlashesActionKeep, + PathWithEscapedSlashesActionReject, + PathWithEscapedSlashesActionUnescapeAndRedirect, + PathWithEscapedSlashesActionUnescapeAndForward, + "", + } + headersWithUnderscoresActions := []string{ + HeadersWithUnderscoresActionAllow, + HeadersWithUnderscoresActionRejectRequest, + HeadersWithUnderscoresActionDropHeader, + "", + } + + if !sliceContains(pathWithEscapedSlashesActions, in.PathWithEscapedSlashesAction) { + errs = append(errs, field.Invalid(path.Child("pathWithEscapedSlashesAction"), in.PathWithEscapedSlashesAction, notInSliceMessage(pathWithEscapedSlashesActions))) + } + if !sliceContains(headersWithUnderscoresActions, in.HeadersWithUnderscoresAction) { + errs = append(errs, field.Invalid(path.Child("headersWithUnderscoresAction"), in.HeadersWithUnderscoresAction, notInSliceMessage(headersWithUnderscoresActions))) + } + return errs +} + // DefaultNamespaceFields has no behaviour here as meshes have no namespace specific fields. func (in *Mesh) DefaultNamespaceFields(_ common.ConsulMeta) { } diff --git a/control-plane/api/v1alpha1/mesh_types_test.go b/control-plane/api/v1alpha1/mesh_types_test.go index 7a925fb0ce..5911e25d4d 100644 --- a/control-plane/api/v1alpha1/mesh_types_test.go +++ b/control-plane/api/v1alpha1/mesh_types_test.go @@ -64,6 +64,14 @@ func TestMesh_MatchesConsul(t *testing.T) { }, HTTP: &MeshHTTPConfig{ SanitizeXForwardedClientCert: true, + Incoming: &MeshDirectionalHTTPConfig{ + RequestNormalization: &RequestNormalizationMeshConfig{ + InsecureDisablePathNormalization: true, // note: this is the opposite of the recommended default + MergeSlashes: true, + PathWithEscapedSlashesAction: "REJECT_REQUEST", + HeadersWithUnderscoresAction: "DROP_HEADER", + }, + }, }, Peering: &PeeringMeshConfig{ PeerThroughMeshGateways: true, @@ -90,6 +98,14 @@ func TestMesh_MatchesConsul(t *testing.T) { }, HTTP: &capi.MeshHTTPConfig{ SanitizeXForwardedClientCert: true, + Incoming: &capi.MeshDirectionalHTTPConfig{ + RequestNormalization: &capi.RequestNormalizationMeshConfig{ + InsecureDisablePathNormalization: true, // note: this is the opposite of the recommended default + MergeSlashes: true, + PathWithEscapedSlashesAction: "REJECT_REQUEST", + HeadersWithUnderscoresAction: "DROP_HEADER", + }, + }, }, Peering: &capi.PeeringMeshConfig{ PeerThroughMeshGateways: true, @@ -168,6 +184,14 @@ func TestMesh_ToConsul(t *testing.T) { }, HTTP: &MeshHTTPConfig{ SanitizeXForwardedClientCert: true, + Incoming: &MeshDirectionalHTTPConfig{ + RequestNormalization: &RequestNormalizationMeshConfig{ + InsecureDisablePathNormalization: true, // note: this is the opposite of the recommended default + MergeSlashes: true, + PathWithEscapedSlashesAction: "REJECT_REQUEST", + HeadersWithUnderscoresAction: "DROP_HEADER", + }, + }, }, Peering: &PeeringMeshConfig{ PeerThroughMeshGateways: true, @@ -194,6 +218,14 @@ func TestMesh_ToConsul(t *testing.T) { }, HTTP: &capi.MeshHTTPConfig{ SanitizeXForwardedClientCert: true, + Incoming: &capi.MeshDirectionalHTTPConfig{ + RequestNormalization: &capi.RequestNormalizationMeshConfig{ + InsecureDisablePathNormalization: true, // note: this is the opposite of the recommended default + MergeSlashes: true, + PathWithEscapedSlashesAction: "REJECT_REQUEST", + HeadersWithUnderscoresAction: "DROP_HEADER", + }, + }, }, Peering: &capi.PeeringMeshConfig{ PeerThroughMeshGateways: true, @@ -367,6 +399,76 @@ func TestMesh_Validate(t *testing.T) { }, }, }, + "http.incoming.requestNormalization.pathWithEscapedSlashesAction valid": { + input: &Mesh{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + }, + Spec: MeshSpec{ + HTTP: &MeshHTTPConfig{ + Incoming: &MeshDirectionalHTTPConfig{ + RequestNormalization: &RequestNormalizationMeshConfig{ + PathWithEscapedSlashesAction: "UNESCAPE_AND_FORWARD", + }, + }, + }, + }, + }, + }, + "http.incoming.requestNormalization.pathWithEscapedSlashesAction invalid": { + input: &Mesh{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + }, + Spec: MeshSpec{ + HTTP: &MeshHTTPConfig{ + Incoming: &MeshDirectionalHTTPConfig{ + RequestNormalization: &RequestNormalizationMeshConfig{ + PathWithEscapedSlashesAction: "foo", + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.http.incoming.requestNormalization.pathWithEscapedSlashesAction: Invalid value: "foo": must be one of "IMPLEMENTATION_SPECIFIC_DEFAULT", "KEEP_UNCHANGED", "REJECT_REQUEST", "UNESCAPE_AND_REDIRECT", "UNESCAPE_AND_FORWARD", ""`, + }, + }, + "http.incoming.requestNormalization.headerWithUnderscoresAction valid": { + input: &Mesh{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + }, + Spec: MeshSpec{ + HTTP: &MeshHTTPConfig{ + Incoming: &MeshDirectionalHTTPConfig{ + RequestNormalization: &RequestNormalizationMeshConfig{ + HeadersWithUnderscoresAction: "REJECT_REQUEST", + }, + }, + }, + }, + }, + }, + "http.incoming.requestNormalization.headersWithUnderscoresAction invalid": { + input: &Mesh{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + }, + Spec: MeshSpec{ + HTTP: &MeshHTTPConfig{ + Incoming: &MeshDirectionalHTTPConfig{ + RequestNormalization: &RequestNormalizationMeshConfig{ + HeadersWithUnderscoresAction: "bar", + }, + }, + }, + }, + }, + expectedErrMsgs: []string{ + `spec.http.incoming.requestNormalization.headersWithUnderscoresAction: Invalid value: "bar": must be one of "ALLOW", "REJECT_REQUEST", "DROP_HEADER", ""`, + }, + }, "multiple errors": { input: &Mesh{ ObjectMeta: metav1.ObjectMeta{ @@ -386,6 +488,14 @@ func TestMesh_Validate(t *testing.T) { Peering: &PeeringMeshConfig{ PeerThroughMeshGateways: true, }, + HTTP: &MeshHTTPConfig{ + Incoming: &MeshDirectionalHTTPConfig{ + RequestNormalization: &RequestNormalizationMeshConfig{ + PathWithEscapedSlashesAction: "foo", + HeadersWithUnderscoresAction: "bar", + }, + }, + }, }, }, consulMeta: common.ConsulMeta{ @@ -398,6 +508,8 @@ func TestMesh_Validate(t *testing.T) { `spec.tls.outgoing.tlsMinVersion: Invalid value: "foo": must be one of "TLS_AUTO", "TLSv1_0", "TLSv1_1", "TLSv1_2", "TLSv1_3", ""`, `spec.tls.outgoing.tlsMaxVersion: Invalid value: "bar": must be one of "TLS_AUTO", "TLSv1_0", "TLSv1_1", "TLSv1_2", "TLSv1_3", ""`, `spec.peering.peerThroughMeshGateways: Forbidden: "peerThroughMeshGateways" is only valid in the "default" partition`, + `spec.http.incoming.requestNormalization.pathWithEscapedSlashesAction: Invalid value: "foo": must be one of "IMPLEMENTATION_SPECIFIC_DEFAULT", "KEEP_UNCHANGED", "REJECT_REQUEST", "UNESCAPE_AND_REDIRECT", "UNESCAPE_AND_FORWARD", ""`, + `spec.http.incoming.requestNormalization.headersWithUnderscoresAction: Invalid value: "bar": must be one of "ALLOW", "REJECT_REQUEST", "DROP_HEADER", ""`, }, }, } diff --git a/control-plane/api/v1alpha1/serviceintentions_types.go b/control-plane/api/v1alpha1/serviceintentions_types.go index d393f72a2d..17d0a9cf2a 100644 --- a/control-plane/api/v1alpha1/serviceintentions_types.go +++ b/control-plane/api/v1alpha1/serviceintentions_types.go @@ -140,8 +140,12 @@ type IntentionHTTPHeaderPermission struct { Prefix string `json:"prefix,omitempty"` // Suffix matches if the header with the given name has this suffix. Suffix string `json:"suffix,omitempty"` + // Contains matches if the header with the given name contains this value. + Contains string `json:"contains,omitempty"` // Regex matches if the header with the given name matches this pattern. Regex string `json:"regex,omitempty"` + // IgnoreCase ignores the case of the header value when matching with exact, prefix, suffix, or contains. + IgnoreCase bool `json:"ignoreCase,omitempty"` // Invert inverts the logic of the match. Invert bool `json:"invert,omitempty"` } @@ -448,13 +452,15 @@ func (in IntentionHTTPHeaderPermissions) toConsul() []capi.IntentionHTTPHeaderPe var headerPermissions []capi.IntentionHTTPHeaderPermission for _, permission := range in { headerPermissions = append(headerPermissions, capi.IntentionHTTPHeaderPermission{ - Name: permission.Name, - Present: permission.Present, - Exact: permission.Exact, - Prefix: permission.Prefix, - Suffix: permission.Suffix, - Regex: permission.Regex, - Invert: permission.Invert, + Name: permission.Name, + Present: permission.Present, + Exact: permission.Exact, + Prefix: permission.Prefix, + Suffix: permission.Suffix, + Contains: permission.Contains, + Regex: permission.Regex, + Invert: permission.Invert, + IgnoreCase: permission.IgnoreCase, }) } @@ -568,10 +574,14 @@ func (in IntentionHTTPHeaderPermissions) validate(path *field.Path) field.ErrorL if permission.Present { hdrParts++ } - hdrParts += numNotEmpty(permission.Exact, permission.Regex, permission.Prefix, permission.Suffix) + hdrParts += numNotEmpty(permission.Exact, permission.Regex, permission.Prefix, permission.Suffix, permission.Contains) if hdrParts > 1 { asJson, _ := json.Marshal(in[i]) - errs = append(errs, field.Invalid(path.Index(i), string(asJson), `at most only one of exact, prefix, suffix, regex, or present may be configured.`)) + errs = append(errs, field.Invalid(path.Index(i), string(asJson), `at most only one of exact, prefix, suffix, contains, regex, or present may be configured.`)) + } + if permission.IgnoreCase && (permission.Present || permission.Regex != "") { + asJson, _ := json.Marshal(in[i]) + errs = append(errs, field.Invalid(path.Index(i), string(asJson), `should set one of exact, prefix, suffix, or contains when using ignoreCase.`)) } } return errs diff --git a/control-plane/api/v1alpha1/serviceintentions_types_test.go b/control-plane/api/v1alpha1/serviceintentions_types_test.go index 8d0a6d907a..fb2ae743b5 100644 --- a/control-plane/api/v1alpha1/serviceintentions_types_test.go +++ b/control-plane/api/v1alpha1/serviceintentions_types_test.go @@ -151,13 +151,15 @@ func TestServiceIntentions_MatchesConsul(t *testing.T) { PathRegex: "/baz", Header: IntentionHTTPHeaderPermissions{ { - Name: "header", - Present: true, - Exact: "exact", - Prefix: "prefix", - Suffix: "suffix", - Regex: "regex", - Invert: true, + Name: "header", + Present: true, + Exact: "exact", + Prefix: "prefix", + Suffix: "suffix", + Contains: "contains", + Regex: "regex", + Invert: true, + IgnoreCase: true, }, }, Methods: []string{ @@ -232,13 +234,15 @@ func TestServiceIntentions_MatchesConsul(t *testing.T) { PathRegex: "/baz", Header: []capi.IntentionHTTPHeaderPermission{ { - Name: "header", - Present: true, - Exact: "exact", - Prefix: "prefix", - Suffix: "suffix", - Regex: "regex", - Invert: true, + Name: "header", + Present: true, + Exact: "exact", + Prefix: "prefix", + Suffix: "suffix", + Contains: "contains", + Regex: "regex", + Invert: true, + IgnoreCase: true, }, }, Methods: []string{ @@ -414,13 +418,15 @@ func TestServiceIntentions_ToConsul(t *testing.T) { PathRegex: "/baz", Header: IntentionHTTPHeaderPermissions{ { - Name: "header", - Present: true, - Exact: "exact", - Prefix: "prefix", - Suffix: "suffix", - Regex: "regex", - Invert: true, + Name: "header", + Present: true, + Exact: "exact", + Prefix: "prefix", + Suffix: "suffix", + Contains: "contains", + Regex: "regex", + Invert: true, + IgnoreCase: true, }, }, Methods: []string{ @@ -500,13 +506,15 @@ func TestServiceIntentions_ToConsul(t *testing.T) { PathRegex: "/baz", Header: []capi.IntentionHTTPHeaderPermission{ { - Name: "header", - Present: true, - Exact: "exact", - Prefix: "prefix", - Suffix: "suffix", - Regex: "regex", - Invert: true, + Name: "header", + Present: true, + Exact: "exact", + Prefix: "prefix", + Suffix: "suffix", + Contains: "contains", + Regex: "regex", + Invert: true, + IgnoreCase: true, }, }, Methods: []string{ @@ -1258,11 +1266,26 @@ func TestServiceIntentions_Validate(t *testing.T) { Suffix: "bar", Regex: "foo", }, + { + Name: "suffix-contains", + Suffix: "bar", + Contains: "foo", + }, { Name: "regex-present", Present: true, Regex: "foobar", }, + { + Name: "present-ignoreCase", + Present: true, + IgnoreCase: true, + }, + { + Name: "regex-ignoreCase", + Regex: "foobar", + IgnoreCase: true, + }, }, }, }, @@ -1273,11 +1296,14 @@ func TestServiceIntentions_Validate(t *testing.T) { }, namespacesEnabled: true, expectedErrMsgs: []string{ - `spec.sources[0].permissions[0].header[0]: Invalid value: "{\"name\":\"exact-present\",\"present\":true,\"exact\":\"foobar\"}": at most only one of exact, prefix, suffix, regex, or present may be configured.`, - `spec.sources[0].permissions[0].header[1]: Invalid value: "{\"name\":\"prefix-exact\",\"exact\":\"foobar\",\"prefix\":\"barfood\"}": at most only one of exact, prefix, suffix, regex, or present may be configured.`, - `spec.sources[0].permissions[0].header[2]: Invalid value: "{\"name\":\"suffix-prefix\",\"prefix\":\"foo\",\"suffix\":\"bar\"}": at most only one of exact, prefix, suffix, regex, or present may be configured.`, - `spec.sources[0].permissions[0].header[3]: Invalid value: "{\"name\":\"suffix-regex\",\"suffix\":\"bar\",\"regex\":\"foo\"}": at most only one of exact, prefix, suffix, regex, or present may be configured.`, - `spec.sources[0].permissions[0].header[4]: Invalid value: "{\"name\":\"regex-present\",\"present\":true,\"regex\":\"foobar\"}": at most only one of exact, prefix, suffix, regex, or present may be configured.`, + `spec.sources[0].permissions[0].header[0]: Invalid value: "{\"name\":\"exact-present\",\"present\":true,\"exact\":\"foobar\"}": at most only one of exact, prefix, suffix, contains, regex, or present may be configured.`, + `spec.sources[0].permissions[0].header[1]: Invalid value: "{\"name\":\"prefix-exact\",\"exact\":\"foobar\",\"prefix\":\"barfood\"}": at most only one of exact, prefix, suffix, contains, regex, or present may be configured.`, + `spec.sources[0].permissions[0].header[2]: Invalid value: "{\"name\":\"suffix-prefix\",\"prefix\":\"foo\",\"suffix\":\"bar\"}": at most only one of exact, prefix, suffix, contains, regex, or present may be configured.`, + `spec.sources[0].permissions[0].header[3]: Invalid value: "{\"name\":\"suffix-regex\",\"suffix\":\"bar\",\"regex\":\"foo\"}": at most only one of exact, prefix, suffix, contains, regex, or present may be configured.`, + `spec.sources[0].permissions[0].header[4]: Invalid value: "{\"name\":\"suffix-contains\",\"suffix\":\"bar\",\"contains\":\"foo\"}": at most only one of exact, prefix, suffix, contains, regex, or present may be configured.`, + `spec.sources[0].permissions[0].header[5]: Invalid value: "{\"name\":\"regex-present\",\"present\":true,\"regex\":\"foobar\"}": at most only one of exact, prefix, suffix, contains, regex, or present may be configured.`, + `spec.sources[0].permissions[0].header[6]: Invalid value: "{\"name\":\"present-ignoreCase\",\"present\":true,\"ignoreCase\":true}": should set one of exact, prefix, suffix, or contains when using ignoreCase.`, + `spec.sources[0].permissions[0].header[7]: Invalid value: "{\"name\":\"regex-ignoreCase\",\"regex\":\"foobar\",\"ignoreCase\":true}": should set one of exact, prefix, suffix, or contains when using ignoreCase.`, }, }, "invalid permissions.action": { diff --git a/control-plane/catalog/metrics/metrics.go b/control-plane/catalog/metrics/metrics.go index 17fabf0bdc..0979cb6f38 100644 --- a/control-plane/catalog/metrics/metrics.go +++ b/control-plane/catalog/metrics/metrics.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package metrics import ( diff --git a/control-plane/cni/go.mod b/control-plane/cni/go.mod index 4b020f0b42..8399dfaba7 100644 --- a/control-plane/cni/go.mod +++ b/control-plane/cni/go.mod @@ -40,11 +40,11 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/net v0.24.0 // indirect + golang.org/x/net v0.34.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/term v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/control-plane/cni/go.sum b/control-plane/cni/go.sum index 81071fa5b8..faea2c58d8 100644 --- a/control-plane/cni/go.sum +++ b/control-plane/cni/go.sum @@ -149,8 +149,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -176,17 +176,17 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -194,8 +194,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml index c5c15b3c5d..51ac810778 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml @@ -62,10 +62,55 @@ spec: http: description: HTTP defines the HTTP configuration for the service mesh. properties: + incoming: + description: Incoming configures settings for incoming HTTP traffic + to mesh proxies. + properties: + requestNormalization: + description: |- + RequestNormalizationMeshConfig contains options pertaining to the + normalization of HTTP requests processed by mesh proxies. + properties: + headersWithUnderscoresAction: + description: |- + HeadersWithUnderscoresAction sets the value of the \`headers_with_underscores_action\` option in the Envoy + listener's \`HttpConnectionManager\` under \`common_http_protocol_options\`. The default value of this option is + empty, which is equivalent to \`ALLOW\`. Refer to the Envoy documentation for more information on available + options. + type: string + insecureDisablePathNormalization: + description: |- + InsecureDisablePathNormalization sets the value of the \`normalize_path\` option in the Envoy listener's + `HttpConnectionManager`. The default value is \`false\`. When set to \`true\` in Consul, \`normalize_path\` is + set to \`false\` for the Envoy proxy. This parameter disables the normalization of request URL paths according to + RFC 3986, conversion of \`\\\` to \`/\`, and decoding non-reserved %-encoded characters. When using L7 intentions + with path match rules, we recommend enabling path normalization in order to avoid match rule circumvention with + non-normalized path values. + type: boolean + mergeSlashes: + description: |- + MergeSlashes sets the value of the \`merge_slashes\` option in the Envoy listener's \`HttpConnectionManager\`. + The default value is \`false\`. This option controls the normalization of request URL paths by merging + consecutive \`/\` characters. This normalization is not part of RFC 3986. When using L7 intentions with path + match rules, we recommend enabling this setting to avoid match rule circumvention through non-normalized path + values, unless legitimate service traffic depends on allowing for repeat \`/\` characters, or upstream services + are configured to differentiate between single and multiple slashes. + type: boolean + pathWithEscapedSlashesAction: + description: |- + PathWithEscapedSlashesAction sets the value of the \`path_with_escaped_slashes_action\` option in the Envoy + listener's \`HttpConnectionManager\`. The default value of this option is empty, which is equivalent to + \`IMPLEMENTATION_SPECIFIC_DEFAULT\`. This parameter controls the action taken in response to request URL paths + with escaped slashes in the path. When using L7 intentions with path match rules, we recommend enabling this + setting to avoid match rule circumvention through non-normalized path values, unless legitimate service traffic + depends on allowing for escaped \`/\` or \`\\\` characters, or upstream services are configured to differentiate + between escaped and unescaped slashes. Refer to the Envoy documentation for more information on available + options. + type: string + type: object + type: object sanitizeXForwardedClientCert: type: boolean - required: - - sanitizeXForwardedClientCert type: object peering: description: Peering defines the peering configuration for the service diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml index 957295b18e..04b5c08522 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml @@ -164,10 +164,19 @@ spec: If more than one is configured all must match for the overall match to apply. items: properties: + contains: + description: Contains matches if the header + with the given name contains this value. + type: string exact: description: Exact matches if the header with the given name is this value. type: string + ignoreCase: + description: IgnoreCase ignores the case of + the header value when matching with exact, + prefix, suffix, or contains. + type: boolean invert: description: Invert inverts the logic of the match. diff --git a/control-plane/go.mod b/control-plane/go.mod index 3e23f2a593..91d5126763 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -16,7 +16,7 @@ require ( github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20240226161840-f3842c41cb2b github.com/hashicorp/consul-k8s/version v0.0.0 github.com/hashicorp/consul-server-connection-manager v0.1.6 - github.com/hashicorp/consul/api v1.29.4 + github.com/hashicorp/consul/api v1.30.0 github.com/hashicorp/consul/sdk v0.16.1 github.com/hashicorp/go-bexpr v0.1.11 github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 @@ -37,7 +37,7 @@ require ( github.com/stretchr/testify v1.8.4 go.uber.org/zap v1.25.0 golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 - golang.org/x/text v0.17.0 + golang.org/x/text v0.21.0 golang.org/x/time v0.3.0 gomodules.xyz/jsonpatch/v2 v2.4.0 google.golang.org/grpc v1.58.3 @@ -142,13 +142,13 @@ require ( github.com/vmware/govmomi v0.18.0 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect + golang.org/x/net v0.34.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect golang.org/x/tools v0.24.0 // indirect google.golang.org/api v0.126.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index 79bf109c1d..79e3dc9094 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -214,8 +214,8 @@ github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20240226161840-f3842c41 github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20240226161840-f3842c41cb2b/go.mod h1:9NKJHOcgmz/6P2y6MegNIOXhIKE/0ils/mHWd5sZgoU= github.com/hashicorp/consul-server-connection-manager v0.1.6 h1:ktj8Fi+dRXn9hhM+FXsfEJayhzzgTqfH08Ne5M6Fmug= github.com/hashicorp/consul-server-connection-manager v0.1.6/go.mod h1:HngMIv57MT+pqCVeRQMa1eTB5dqnyMm8uxjyv+Hn8cs= -github.com/hashicorp/consul/api v1.29.4 h1:P6slzxDLBOxUSj3fWo2o65VuKtbtOXFi7TSSgtXutuE= -github.com/hashicorp/consul/api v1.29.4/go.mod h1:HUlfw+l2Zy68ceJavv2zAyArl2fqhGWnMycyt56sBgg= +github.com/hashicorp/consul/api v1.30.0 h1:ArHVMMILb1nQv8vZSGIwwQd2gtc+oSQZ6CalyiyH2XQ= +github.com/hashicorp/consul/api v1.30.0/go.mod h1:B2uGchvaXVW2JhFoS8nqTxMD5PBykr4ebY4JWHTTeLM= github.com/hashicorp/consul/proto-public v0.6.2 h1:+DA/3g/IiKlJZb88NBn0ZgXrxJp2NlvCZdEyl+qxvL0= github.com/hashicorp/consul/proto-public v0.6.2/go.mod h1:cXXbOg74KBNGajC+o8RlA502Esf0R9prcoJgiOX/2Tg= github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg= @@ -480,8 +480,8 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= @@ -524,8 +524,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= @@ -539,8 +539,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -584,16 +584,16 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -604,8 +604,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/control-plane/helper/test/test_util.go b/control-plane/helper/test/test_util.go index 542700c34f..a2492143a7 100644 --- a/control-plane/helper/test/test_util.go +++ b/control-plane/helper/test/test_util.go @@ -282,7 +282,7 @@ func SetupK8sAuthMethodWithNamespaces(t *testing.T, consulClient *api.Client, se Description: "Kubernetes binding rule", AuthMethod: AuthMethod, BindType: api.BindingRuleBindTypeTemplatedPolicy, - BindName: api.ACLTemplatedPolicyWorkloadIdentityName, + BindName: "builtin/workload-identity", // TODO: remove w/ v2 code BindVars: &api.ACLTemplatedPolicyVariables{ Name: "${serviceaccount.name}", }, diff --git a/control-plane/subcommand/common/metrics_util.go b/control-plane/subcommand/common/metrics_util.go index d3866fdeef..343b4c9e51 100644 --- a/control-plane/subcommand/common/metrics_util.go +++ b/control-plane/subcommand/common/metrics_util.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package common import "strconv" diff --git a/control-plane/subcommand/gateway-resources/command_test.go b/control-plane/subcommand/gateway-resources/command_test.go index 6e8230102c..ef88886004 100644 --- a/control-plane/subcommand/gateway-resources/command_test.go +++ b/control-plane/subcommand/gateway-resources/command_test.go @@ -161,9 +161,14 @@ func TestRun_flagValidation(t *testing.T) { flagNodeSelector: ` foo: 1 bar: 2`, - flagTolerations: ` -- value: foo -- value: bar`, + flagTolerations: `- "operator": "Equal" + "effect": "NoSchedule" + "key": "node" + "value": "clients" +- "operator": "Equal" + "effect": "NoSchedule" + "key": "node2" + "value": "clients2"`, flagServiceAnnotations: ` - foo - bar`, diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index 68addfc7b2..125c46ea2a 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -48,6 +48,7 @@ type Command struct { flagListen string flagCertDir string // Directory with TLS certs for listening (PEM) flagDefaultInject bool // True to inject by default + flagConfigFile string // Path to a config file in JSON format flagConsulImage string // Docker image for Consul flagConsulDataplaneImage string // Docker image for Envoy flagConsulK8sImage string // Docker image for consul-k8s @@ -174,6 +175,7 @@ func init() { func (c *Command) init() { c.flagSet = flag.NewFlagSet("", flag.ContinueOnError) c.flagSet.StringVar(&c.flagListen, "listen", ":8080", "Address to bind listener to.") + c.flagSet.StringVar(&c.flagConfigFile, "config-file", "", "Path to a JSON config file.") c.flagSet.Var((*flags.FlagMapValue)(&c.flagNodeMeta), "node-meta", "Metadata to set on the node, formatted as key=value. This flag may be specified multiple times to set multiple meta fields.") c.flagSet.BoolVar(&c.flagDefaultInject, "default-inject", true, "Inject by default.") diff --git a/control-plane/subcommand/inject-connect/v1controllers.go b/control-plane/subcommand/inject-connect/v1controllers.go index fa6e36d507..dd6b3be739 100644 --- a/control-plane/subcommand/inject-connect/v1controllers.go +++ b/control-plane/subcommand/inject-connect/v1controllers.go @@ -5,10 +5,12 @@ package connectinject import ( "context" + "encoding/json" "fmt" "os" "github.com/hashicorp/consul-server-connection-manager/discovery" + v1 "k8s.io/api/core/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -31,6 +33,23 @@ func (c *Command) configureControllers(ctx context.Context, mgr manager.Manager, // Create Consul API config object. consulConfig := c.consul.ConsulClientConfig() + type FileConfig struct { + ImagePullSecrets []v1.LocalObjectReference `json:"image_pull_secrets"` + } + + var cfgFile FileConfig + if c.flagConfigFile != "" { + if file, err := os.ReadFile(c.flagConfigFile); err != nil { + setupLog.Info("Failed to read specified -config-file", "file", c.flagConfigFile, "error", err) + } else { + if err := json.Unmarshal(file, &cfgFile); err != nil { + setupLog.Error(err, "Config file present but could not be deserialized, will use defaults", "file", c.flagConfigFile) + } else { + setupLog.Info("Config file present and deserialized", "file", c.flagConfigFile, "config", cfgFile) + } + } + } + // Convert allow/deny lists to sets. allowK8sNamespaces := flags.ToSet(c.flagAllowK8sNamespacesList) denyK8sNamespaces := flags.ToSet(c.flagDenyK8sNamespacesList) @@ -118,6 +137,7 @@ func (c *Command) configureControllers(ctx context.Context, mgr manager.Manager, }, ImageDataplane: c.flagConsulDataplaneImage, ImageConsulK8S: c.flagConsulK8sImage, + ImagePullSecrets: cfgFile.ImagePullSecrets, GlobalImagePullPolicy: c.flagGlobalImagePullPolicy, ConsulDestinationNamespace: c.flagConsulDestinationNamespace, NamespaceMirroringPrefix: c.flagK8SNSMirroringPrefix, diff --git a/control-plane/subcommand/server-acl-init/rules.go b/control-plane/subcommand/server-acl-init/rules.go index ae145fae43..3cbac881f5 100644 --- a/control-plane/subcommand/server-acl-init/rules.go +++ b/control-plane/subcommand/server-acl-init/rules.go @@ -310,11 +310,11 @@ partition "{{ .PartitionName }}" { } {{- if .EnableNamespaces }} namespace_prefix "" { + acl = "write" {{- end }} {{- if .EnablePartitions }} policy = "write" {{- end }} - acl = "write" service_prefix "" { policy = "write" intentions = "write" diff --git a/control-plane/subcommand/server-acl-init/rules_test.go b/control-plane/subcommand/server-acl-init/rules_test.go index a31ca5dcd5..b57b511a2f 100644 --- a/control-plane/subcommand/server-acl-init/rules_test.go +++ b/control-plane/subcommand/server-acl-init/rules_test.go @@ -883,7 +883,6 @@ func TestInjectRules(t *testing.T) { node_prefix "" { policy = "write" } - acl = "write" service_prefix "" { policy = "write" intentions = "write" @@ -917,7 +916,30 @@ func TestInjectRules(t *testing.T) { }`, }, { - EnableNamespaces: true, + EnableNamespaces: false, + EnablePartitions: true, + EnablePeering: false, + PartitionName: "part-1", + Expected: ` +partition "part-1" { + mesh = "write" + acl = "write" + node_prefix "" { + policy = "write" + } + policy = "write" + service_prefix "" { + policy = "write" + intentions = "write" + } + identity_prefix "" { + policy = "write" + intentions = "write" + } +}`, + }, + { + EnableNamespaces: false, EnablePartitions: false, EnablePeering: true, Expected: ` @@ -928,8 +950,6 @@ func TestInjectRules(t *testing.T) { node_prefix "" { policy = "write" } - namespace_prefix "" { - acl = "write" service_prefix "" { policy = "write" intentions = "write" @@ -937,8 +957,7 @@ func TestInjectRules(t *testing.T) { identity_prefix "" { policy = "write" intentions = "write" - } - }`, + }`, }, { EnableNamespaces: true, @@ -953,7 +972,32 @@ partition "part-1" { policy = "write" } namespace_prefix "" { + acl = "write" policy = "write" + service_prefix "" { + policy = "write" + intentions = "write" + } + identity_prefix "" { + policy = "write" + intentions = "write" + } + } +}`, + }, + { + EnableNamespaces: true, + EnablePartitions: false, + EnablePeering: true, + Expected: ` + mesh = "write" + operator = "write" + acl = "write" + peering = "write" + node_prefix "" { + policy = "write" + } + namespace_prefix "" { acl = "write" service_prefix "" { policy = "write" @@ -963,7 +1007,30 @@ partition "part-1" { policy = "write" intentions = "write" } + }`, + }, + { + EnableNamespaces: false, + EnablePartitions: true, + EnablePeering: true, + PartitionName: "part-1", + Expected: ` +partition "part-1" { + mesh = "write" + acl = "write" + peering = "write" + node_prefix "" { + policy = "write" } + policy = "write" + service_prefix "" { + policy = "write" + intentions = "write" + } + identity_prefix "" { + policy = "write" + intentions = "write" + } }`, }, { @@ -980,8 +1047,8 @@ partition "part-1" { policy = "write" } namespace_prefix "" { - policy = "write" acl = "write" + policy = "write" service_prefix "" { policy = "write" intentions = "write" diff --git a/control-plane/subcommand/sync-catalog/command.go b/control-plane/subcommand/sync-catalog/command.go index c3965165ed..36bb996f2f 100644 --- a/control-plane/subcommand/sync-catalog/command.go +++ b/control-plane/subcommand/sync-catalog/command.go @@ -12,20 +12,17 @@ import ( "os" "os/signal" "regexp" - "strings" "sync" "syscall" "time" "github.com/armon/go-metrics/prometheus" - "github.com/cenkalti/backoff" mapset "github.com/deckarep/golang-set" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" "github.com/prometheus/client_golang/prometheus/promhttp" - "golang.org/x/sync/errgroup" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" @@ -37,7 +34,6 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/helper/controller" "github.com/hashicorp/consul-k8s/control-plane/subcommand" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - metricsutil "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) @@ -67,7 +63,7 @@ type Command struct { flagAddK8SNamespaceSuffix bool flagLogLevel string flagLogJSON bool - flagPurgeK8SServicesFromNode string + flagPurgeK8SServicesFromNode bool flagFilter string // Flags to support namespaces @@ -156,8 +152,8 @@ func (c *Command) init() { "\"debug\", \"info\", \"warn\", and \"error\".") c.flags.BoolVar(&c.flagLogJSON, "log-json", false, "Enable or disable JSON output format for logging.") - c.flags.StringVar(&c.flagPurgeK8SServicesFromNode, "purge-k8s-services-from-node", "", - "Specifies the name of the Consul node for which to deregister synced Kubernetes services.") + c.flags.BoolVar(&c.flagPurgeK8SServicesFromNode, "purge-k8s-services-from-node", false, + "Specifies if services should be purged from the Consul node. If set, all K8S services will be removed from the Consul node.") c.flags.StringVar(&c.flagFilter, "filter", "", "Specifies the expression used to filter the services on the Consul node that will be deregistered. "+ "The syntax for this filter is the same as the syntax used in the List Services for Node API in the Consul catalog.") @@ -279,7 +275,7 @@ func (c *Command) Run(args []string) int { } c.ready = true - if c.flagPurgeK8SServicesFromNode != "" { + if c.flagPurgeK8SServicesFromNode { consulClient, err := consul.NewClientFromConnMgr(consulConfig, c.connMgr) if err != nil { c.UI.Error(fmt.Sprintf("unable to instantiate consul client: %s", err)) @@ -462,64 +458,15 @@ func (c *Command) Run(args []string) int { // remove all k8s services from Consul. func (c *Command) removeAllK8SServicesFromConsulNode(consulClient *api.Client) error { - node, _, err := consulClient.Catalog().NodeServiceList(c.flagPurgeK8SServicesFromNode, &api.QueryOptions{Filter: c.flagFilter}) + _, err := consulClient.Catalog().Deregister(&api.CatalogDeregistration{ + Node: c.flagConsulNodeName, + Partition: c.consul.Partition, + }, nil) if err != nil { + c.UI.Error(fmt.Sprintf("unable to deregister all K8S services from Consul: %s", err)) return err } - var firstErr error - services := node.Services - batchSize := 300 - maxRetries := 2 - retryDelay := 200 * time.Millisecond - - // Ask for user confirmation before purging services - for { - c.UI.Info(fmt.Sprintf("Are you sure you want to delete %v K8S services from %v? (y/n): ", len(services), c.flagPurgeK8SServicesFromNode)) - var input string - fmt.Scanln(&input) - if input = strings.ToLower(input); input == "y" { - break - } else if input == "n" { - return nil - } else { - c.UI.Info("Invalid input. Please enter 'y' or 'n'.") - } - } - - for i := 0; i < len(services); i += batchSize { - end := i + batchSize - if end > len(services) { - end = len(services) - } - - var eg errgroup.Group - for _, service := range services[i:end] { - s := service - eg.Go(func() error { - var b backoff.BackOff = backoff.NewConstantBackOff(retryDelay) - b = backoff.WithMaxRetries(b, uint64(maxRetries)) - return backoff.Retry(func() error { - _, err := consulClient.Catalog().Deregister(&api.CatalogDeregistration{ - Node: c.flagPurgeK8SServicesFromNode, - ServiceID: s.ID, - }, nil) - return err - }, b) - }) - } - if err := eg.Wait(); err != nil { - if firstErr == nil { - c.UI.Info("Some K8S services were not deregistered from Consul") - firstErr = err - } - } - c.UI.Info(fmt.Sprintf("Processed %v K8S services from %v", end-i, c.flagPurgeK8SServicesFromNode)) - } - - if firstErr != nil { - return firstErr - } c.UI.Info("All K8S services were deregistered from Consul") return nil } @@ -553,7 +500,7 @@ func (c *Command) validateFlags() error { // For the Consul node name to be discoverable via DNS, it must contain only // dashes and alphanumeric characters. Length is also constrained. // These restrictions match those defined in Consul's agent definition. - var invalidDnsRe = regexp.MustCompile(`[^A-Za-z0-9\\-]+`) + invalidDnsRe := regexp.MustCompile(`[^A-Za-z0-9\\-]+`) const maxDNSLabelLength = 63 if invalidDnsRe.MatchString(c.flagConsulNodeName) { @@ -570,7 +517,7 @@ func (c *Command) validateFlags() error { } if c.flagMetricsPort != "" { - if _, valid := metricsutil.ParseScrapePort(c.flagMetricsPort); !valid { + if _, valid := common.ParseScrapePort(c.flagMetricsPort); !valid { return errors.New("-metrics-port must be a valid unprivileged port number") } } @@ -586,7 +533,7 @@ func (c *Command) recordMetrics() (*prometheus.PrometheusSink, error) { return &prometheus.PrometheusSink{}, err } - var counters = [][]prometheus.CounterDefinition{ + counters := [][]prometheus.CounterDefinition{ catalogtoconsul.SyncToConsulCounters, catalogtok8s.SyncToK8sCounters, } @@ -621,8 +568,9 @@ func (c *Command) authorizeMiddleware() func(http.Handler) http.Handler { } } -const synopsis = "Sync Kubernetes services and Consul services." -const help = ` +const ( + synopsis = "Sync Kubernetes services and Consul services." + help = ` Usage: consul-k8s-control-plane sync-catalog [options] Sync K8S pods, services, and more with the Consul service catalog. @@ -631,3 +579,4 @@ Usage: consul-k8s-control-plane sync-catalog [options] K8S services. ` +) diff --git a/control-plane/subcommand/sync-catalog/command_ent_test.go b/control-plane/subcommand/sync-catalog/command_ent_test.go index f8bdccda6b..231b2755c6 100644 --- a/control-plane/subcommand/sync-catalog/command_ent_test.go +++ b/control-plane/subcommand/sync-catalog/command_ent_test.go @@ -272,7 +272,6 @@ func TestRun_ToConsulMirroringNamespaces(t *testing.T) { } } - }) }) } @@ -706,3 +705,152 @@ func TestRun_ToConsulNamespacesACLs(t *testing.T) { }) } } + +// Test services could be de-registered from Consul. +func TestRemoveAllK8SServicesFromConsulWithPartitions(t *testing.T) { + testCases := map[string]struct { + nodeName string + partitionName string + }{ + "default Node name in default partition": { + nodeName: "k8s-sync", + partitionName: "default", + }, + "non-default Node name in default partition": { + nodeName: "custom-node", + partitionName: "default", + }, + "default Node name in non-default partition": { + nodeName: "k8s-sync", + partitionName: "part-1", + }, + "non-default Node name in non-default partition": { + nodeName: "custom-node", + partitionName: "part-1", + }, + } + + for name, tc := range testCases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + var err error + + k8s, testClient := completeSetup(t) + consulClient := testClient.APIClient + + // Create a mock reader to simulate user input + // Run the command. + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + clientset: k8s, + logger: hclog.New(&hclog.LoggerOptions{ + Name: t.Name(), + Level: hclog.Debug, + }), + flagAllowK8sNamespacesList: []string{"*"}, + connMgr: testClient.Watcher, + } + + otherPartitionName := "the-other-one" + + // create another partition and register 2 services there to the same node to show that we won't delete them + _, _, err = consulClient.Partitions().Create(context.Background(), &api.Partition{Name: otherPartitionName}, nil) + require.NoError(t, err) + + _, err = consulClient.Catalog().Register( + &api.CatalogRegistration{ + Node: tc.nodeName, + Address: "5.5.5.5", + Service: &api.AgentService{ + ID: "other-service-1", + Service: "other-service-1", + Tags: []string{"other-k8s-cluster"}, + Meta: map[string]string{}, + Port: 0, + Address: "5.5.5.5", + Partition: otherPartitionName, + }, + Partition: otherPartitionName, + }, + &api.WriteOptions{Partition: otherPartitionName}, + ) + require.NoError(t, err) + + _, err = consulClient.Catalog().Register( + &api.CatalogRegistration{ + Node: tc.nodeName, + Address: "6.6.6.6", + Service: &api.AgentService{ + ID: "other-service-2", + Service: "other-service-2", + Tags: []string{"other-k8s-cluster"}, + Meta: map[string]string{}, + Port: 0, + Address: "6.6.6.6", + Partition: otherPartitionName, + }, + Partition: otherPartitionName, + }, + &api.WriteOptions{Partition: otherPartitionName}, + ) + require.NoError(t, err) + + // create partition if it does not exist + if tc.partitionName != "default" { + p, _, err := consulClient.Partitions().Create(context.Background(), &api.Partition{Name: tc.partitionName}, nil) + require.NoError(t, err) + require.NotNil(t, p) + } + + // create two services in k8s + _, err = k8s.CoreV1().Services("bar").Create(context.Background(), lbService("foo", "1.1.1.1"), metav1.CreateOptions{}) + require.NoError(t, err) + + _, err = k8s.CoreV1().Services("baz").Create(context.Background(), lbService("foo", "2.2.2.2"), metav1.CreateOptions{}) + require.NoError(t, err) + + longRunningChan := runCommandAsynchronously(&cmd, []string{ + "-addresses", "127.0.0.1", + "-http-port", strconv.Itoa(testClient.Cfg.HTTPPort), + "-consul-write-interval", "100ms", + "-partition", tc.partitionName, + "-consul-node-name", tc.nodeName, + "-add-k8s-namespace-suffix", + }) + + // check that the two K8s services have been synced into Consul + retry.Run(t, func(r *retry.R) { + svc, _, err := consulClient.Catalog().Service("foo-bar", "k8s", &api.QueryOptions{Partition: tc.partitionName}) + require.NoError(r, err) + require.Len(r, svc, 1) + require.Equal(r, "1.1.1.1", svc[0].ServiceAddress) + svc, _, err = consulClient.Catalog().Service("foo-baz", "k8s", &api.QueryOptions{Partition: tc.partitionName}) + require.NoError(r, err) + require.Len(r, svc, 1) + require.Equal(r, "2.2.2.2", svc[0].ServiceAddress) + }) + + defer stopCommand(t, &cmd, longRunningChan) + + exitChan := runCommandAsynchronously(&cmd, []string{ + "-addresses", "127.0.0.1", + "-http-port", strconv.Itoa(testClient.Cfg.HTTPPort), + "-purge-k8s-services-from-node=true", + "-partition", tc.partitionName, + "-consul-node-name", tc.nodeName, + }) + stopCommand(t, &cmd, exitChan) + + retry.Run(t, func(r *retry.R) { + serviceList, _, err := consulClient.Catalog().NodeServiceList(tc.nodeName, &api.QueryOptions{AllowStale: false, Partition: tc.partitionName}) + require.NoError(r, err) + require.Len(r, serviceList.Services, 0) + otherPartitionServiceList, _, err := consulClient.Catalog().NodeServiceList(tc.nodeName, &api.QueryOptions{AllowStale: false, Partition: otherPartitionName}) + require.NoError(r, err) + require.Len(r, otherPartitionServiceList.Services, 2) + }) + }) + } +} diff --git a/control-plane/subcommand/sync-catalog/command_test.go b/control-plane/subcommand/sync-catalog/command_test.go index b6aa63a903..d4d011b7e0 100644 --- a/control-plane/subcommand/sync-catalog/command_test.go +++ b/control-plane/subcommand/sync-catalog/command_test.go @@ -560,160 +560,124 @@ func TestRun_ToConsulChangingFlags(t *testing.T) { // Test services could be de-registered from Consul. func TestRemoveAllK8SServicesFromConsul(t *testing.T) { - t.Parallel() - - k8s, testClient := completeSetup(t) - consulClient := testClient.APIClient - - // Create a mock reader to simulate user input - input := "y\n" - reader, writer, err := os.Pipe() - require.NoError(t, err) - oldStdin := os.Stdin - os.Stdin = reader - defer func() { os.Stdin = oldStdin }() - - // Write the simulated user input to the mock reader - go func() { - defer writer.Close() - _, err := writer.WriteString(input) - require.NoError(t, err) - }() - - // Run the command. - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - clientset: k8s, - logger: hclog.New(&hclog.LoggerOptions{ - Name: t.Name(), - Level: hclog.Debug, - }), - flagAllowK8sNamespacesList: []string{"*"}, - connMgr: testClient.Watcher, + testCases := map[string]struct { + nodeToDeregisterName string + }{ + "default Node name in default partition": { + nodeToDeregisterName: "k8s-sync", + }, + "non-default Node name in default partition": { + nodeToDeregisterName: "custom-node", + }, } - // create two services in k8s - _, err = k8s.CoreV1().Services("bar").Create(context.Background(), lbService("foo", "1.1.1.1"), metav1.CreateOptions{}) - require.NoError(t, err) - - _, err = k8s.CoreV1().Services("baz").Create(context.Background(), lbService("foo", "2.2.2.2"), metav1.CreateOptions{}) - require.NoError(t, err) - - longRunningChan := runCommandAsynchronously(&cmd, []string{ - "-addresses", "127.0.0.1", - "-http-port", strconv.Itoa(testClient.Cfg.HTTPPort), - "-consul-write-interval", "100ms", - "-add-k8s-namespace-suffix", - }) - defer stopCommand(t, &cmd, longRunningChan) - - // check that the two K8s services have been synced into Consul - retry.Run(t, func(r *retry.R) { - svc, _, err := consulClient.Catalog().Service("foo-bar", "k8s", nil) - require.NoError(r, err) - require.Len(r, svc, 1) - require.Equal(r, "1.1.1.1", svc[0].ServiceAddress) - svc, _, err = consulClient.Catalog().Service("foo-baz", "k8s", nil) - require.NoError(r, err) - require.Len(r, svc, 1) - require.Equal(r, "2.2.2.2", svc[0].ServiceAddress) - }) + otherNodeName := "other-node" - exitChan := runCommandAsynchronously(&cmd, []string{ - "-addresses", "127.0.0.1", - "-http-port", strconv.Itoa(testClient.Cfg.HTTPPort), - "-purge-k8s-services-from-node=k8s-sync", - }) - stopCommand(t, &cmd, exitChan) + for name, tc := range testCases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + var err error - retry.Run(t, func(r *retry.R) { - serviceList, _, err := consulClient.Catalog().NodeServiceList("k8s-sync", &api.QueryOptions{AllowStale: false}) - require.NoError(r, err) - require.Len(r, serviceList.Services, 0) - }) -} + k8s, testClient := completeSetup(t) + consulClient := testClient.APIClient -// Test services could be de-registered from Consul with filter. -func TestRemoveAllK8SServicesFromConsulWithFilter(t *testing.T) { - t.Parallel() + // Create a mock reader to simulate user input + // Run the command. + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + clientset: k8s, + logger: hclog.New(&hclog.LoggerOptions{ + Name: t.Name(), + Level: hclog.Debug, + }), + flagAllowK8sNamespacesList: []string{"*"}, + connMgr: testClient.Watcher, + } - k8s, testClient := completeSetup(t) - consulClient := testClient.APIClient + _, err = consulClient.Catalog().Register( + &api.CatalogRegistration{ + Node: otherNodeName, + Address: "5.5.5.5", + Service: &api.AgentService{ + ID: "other-service-1", + Service: "other-service-1", + Tags: []string{"other-k8s-cluster"}, + Meta: map[string]string{}, + Port: 0, + Address: "5.5.5.5", + }, + }, + &api.WriteOptions{}, + ) + require.NoError(t, err) + + _, err = consulClient.Catalog().Register( + &api.CatalogRegistration{ + Node: otherNodeName, + Address: "6.6.6.6", + Service: &api.AgentService{ + ID: "other-service-2", + Service: "other-service-2", + Tags: []string{"other-k8s-cluster"}, + Meta: map[string]string{}, + Port: 0, + Address: "6.6.6.6", + }, + }, + &api.WriteOptions{}, + ) + require.NoError(t, err) - // Create a mock reader to simulate user input - input := "y\n" - reader, writer, err := os.Pipe() - require.NoError(t, err) - oldStdin := os.Stdin - os.Stdin = reader - defer func() { os.Stdin = oldStdin }() + // create two services in k8s + _, err = k8s.CoreV1().Services("bar").Create(context.Background(), lbService("foo", "1.1.1.1"), metav1.CreateOptions{}) + require.NoError(t, err) - // Write the simulated user input to the mock reader - go func() { - defer writer.Close() - _, err := writer.WriteString(input) - require.NoError(t, err) - }() + _, err = k8s.CoreV1().Services("baz").Create(context.Background(), lbService("foo", "2.2.2.2"), metav1.CreateOptions{}) + require.NoError(t, err) - // Run the command. - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - clientset: k8s, - logger: hclog.New(&hclog.LoggerOptions{ - Name: t.Name(), - Level: hclog.Debug, - }), - flagAllowK8sNamespacesList: []string{"*"}, - connMgr: testClient.Watcher, - } + longRunningChan := runCommandAsynchronously(&cmd, []string{ + "-addresses", "127.0.0.1", + "-http-port", strconv.Itoa(testClient.Cfg.HTTPPort), + "-consul-write-interval", "100ms", + "-consul-node-name", tc.nodeToDeregisterName, + "-add-k8s-namespace-suffix", + }) - // create two services in k8s - _, err = k8s.CoreV1().Services("bar").Create(context.Background(), lbService("foo", "1.1.1.1"), metav1.CreateOptions{}) - require.NoError(t, err) - _, err = k8s.CoreV1().Services("baz").Create(context.Background(), lbService("foo", "2.2.2.2"), metav1.CreateOptions{}) - require.NoError(t, err) - _, err = k8s.CoreV1().Services("bat").Create(context.Background(), lbService("foo", "3.3.3.3"), metav1.CreateOptions{}) - require.NoError(t, err) + // check that the two K8s services have been synced into Consul + retry.Run(t, func(r *retry.R) { + svc, _, err := consulClient.Catalog().Service("foo-bar", "k8s", &api.QueryOptions{}) + require.NoError(r, err) + require.Len(r, svc, 1) + require.Equal(r, "1.1.1.1", svc[0].ServiceAddress) + svc, _, err = consulClient.Catalog().Service("foo-baz", "k8s", &api.QueryOptions{}) + require.NoError(r, err) + require.Len(r, svc, 1) + require.Equal(r, "2.2.2.2", svc[0].ServiceAddress) + }) - longRunningChan := runCommandAsynchronously(&cmd, []string{ - "-addresses", "127.0.0.1", - "-http-port", strconv.Itoa(testClient.Cfg.HTTPPort), - "-consul-write-interval", "100ms", - "-add-k8s-namespace-suffix", - }) - defer stopCommand(t, &cmd, longRunningChan) + defer stopCommand(t, &cmd, longRunningChan) - // check that the name of the service is namespaced - retry.Run(t, func(r *retry.R) { - svc, _, err := consulClient.Catalog().Service("foo-bar", "k8s", nil) - require.NoError(r, err) - require.Len(r, svc, 1) - require.Equal(r, "1.1.1.1", svc[0].ServiceAddress) - svc, _, err = consulClient.Catalog().Service("foo-baz", "k8s", nil) - require.NoError(r, err) - require.Len(r, svc, 1) - require.Equal(r, "2.2.2.2", svc[0].ServiceAddress) - svc, _, err = consulClient.Catalog().Service("foo-bat", "k8s", nil) - require.NoError(r, err) - require.Len(r, svc, 1) - require.Equal(r, "3.3.3.3", svc[0].ServiceAddress) - }) + exitChan := runCommandAsynchronously(&cmd, []string{ + "-addresses", "127.0.0.1", + "-http-port", strconv.Itoa(testClient.Cfg.HTTPPort), + "-purge-k8s-services-from-node=true", + "-consul-node-name", tc.nodeToDeregisterName, + }) + stopCommand(t, &cmd, exitChan) - exitChan := runCommandAsynchronously(&cmd, []string{ - "-addresses", "127.0.0.1", - "-http-port", strconv.Itoa(testClient.Cfg.HTTPPort), - "-purge-k8s-services-from-node=k8s-sync", - "-filter=baz in ID", - }) - stopCommand(t, &cmd, exitChan) + retry.Run(t, func(r *retry.R) { + serviceList, _, err := consulClient.Catalog().NodeServiceList(tc.nodeToDeregisterName, &api.QueryOptions{AllowStale: false}) + require.NoError(r, err) + require.Len(r, serviceList.Services, 0) - retry.Run(t, func(r *retry.R) { - serviceList, _, err := consulClient.Catalog().NodeServiceList("k8s-sync", &api.QueryOptions{AllowStale: false}) - require.NoError(r, err) - require.Len(r, serviceList.Services, 2) - }) + otherNodeServiceList, _, err := consulClient.Catalog().NodeServiceList(otherNodeName, &api.QueryOptions{AllowStale: false}) + require.NoError(r, err) + require.Len(r, otherNodeServiceList.Services, 2) + }) + }) + } } // Set up test consul agent and fake kubernetes cluster client. diff --git a/version/version.go b/version/version.go index e5d773c66d..523e4377e8 100644 --- a/version/version.go +++ b/version/version.go @@ -17,12 +17,12 @@ var ( // // Version must conform to the format expected by // github.com/hashicorp/go-version for tests to work. - Version = "1.6.0" + Version = "1.6.2" // A pre-release marker for the version. If this is "" (empty string) // then it means that it is a final release. Otherwise, this is a pre-release // such as "dev" (in development), "beta", "rc1", etc. - VersionPrerelease = "dev" + VersionPrerelease = "" ) // GetHumanVersion composes the parts of the version in a way that's suitable